Skip to content

Commit

Permalink
Kaito fed creds and identity changes (Azure#914)
Browse files Browse the repository at this point in the history
  • Loading branch information
hsubramanianaks authored Sep 19, 2024
1 parent e728d20 commit 0c016fd
Show file tree
Hide file tree
Showing 12 changed files with 393 additions and 92 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,6 @@
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.cluster/i",
"group": "9@3"
},
{
"command": "aks.aksKaito",
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.cluster/i"
},
{
"submenu": "aks.kaitoInstallSubMenu",
"when": "view == kubernetes.cloudExplorer && viewItem =~ /aks\\.cluster/i",
Expand Down Expand Up @@ -590,6 +586,7 @@
"@azure/arm-containerservice": "^21.1.0-beta.1",
"@azure/arm-features": "^3.1.0",
"@azure/arm-monitor": "^7.0.0",
"@azure/arm-msi": "^2.1.0",
"@azure/arm-resources": "^5.2.0",
"@azure/arm-resources-subscriptions": "^2.1.0",
"@azure/arm-storage": "^18.3.0",
Expand Down
24 changes: 23 additions & 1 deletion src/commands/aksKaito/aksKaito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import * as vscode from "vscode";
import * as k8s from "vscode-kubernetes-tools-api";
import { getReadySessionProvider } from "../../auth/azureAuth";
import { KaitoPanel, KaitoPanelDataProvider } from "../../panels/KaitoPanel";
import { filterPodName, getAksClusterTreeNode } from "../utils/clusters";
import { filterPodName, getAksClusterTreeNode, getKubernetesClusterInfo } from "../utils/clusters";
import { failed } from "../utils/errorable";
import { getExtension } from "../utils/host";
import * as tmpfile from "../utils/tempfile";

export default async function aksKaito(_context: IActionContext, target: unknown): Promise<void> {
const cloudExplorer = await k8s.extension.cloudExplorer.v1;
const clusterExplorer = await k8s.extension.clusterExplorer.v1;
const kubectl = await k8s.extension.kubectl.v1;

const sessionProvider = await getReadySessionProvider();
Expand All @@ -17,12 +19,28 @@ export default async function aksKaito(_context: IActionContext, target: unknown
return;
}

if (!cloudExplorer.available) {
vscode.window.showWarningMessage(`Cloud explorer is unavailable.`);
return;
}

if (!clusterExplorer.available) {
vscode.window.showWarningMessage(`Cluster explorer is unavailable.`);
return;
}

const clusterNode = getAksClusterTreeNode(target, cloudExplorer);
if (failed(clusterNode)) {
vscode.window.showErrorMessage(clusterNode.error);
return;
}

const clusterInfo = await getKubernetesClusterInfo(sessionProvider.result, target, cloudExplorer, clusterExplorer);
if (failed(clusterInfo)) {
vscode.window.showErrorMessage(clusterInfo.error);
return;
}

const extension = getExtension();
if (failed(extension)) {
vscode.window.showErrorMessage(extension.error);
Expand Down Expand Up @@ -50,13 +68,17 @@ export default async function aksKaito(_context: IActionContext, target: unknown
"kaito-",
);

const kubeConfigFile = await tmpfile.createTempFile(clusterInfo.result.kubeconfigYaml, "yaml");

const dataProvider = new KaitoPanelDataProvider(
clusterName,
subscriptionId,
resourceGroupName,
armId,
sessionProvider.result,
filterKaitoPodNames.succeeded ? filterKaitoPodNames.result : [],
kubectl,
kubeConfigFile.filePath,
);

panel.show(dataProvider);
Expand Down
32 changes: 18 additions & 14 deletions src/commands/utils/arm.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { SubscriptionClient } from "@azure/arm-resources-subscriptions";
import { getCredential, getEnvironment } from "../../auth/azureAuth";
import { PagedAsyncIterableIterator } from "@azure/core-paging";
import { Errorable, getErrorMessage } from "./errorable";
import { ResourceManagementClient } from "@azure/arm-resources";
import { AuthorizationManagementClient } from "@azure/arm-authorization";
import { ContainerRegistryManagementClient } from "@azure/arm-containerregistry";
import { ContainerServiceClient } from "@azure/arm-containerservice";
import { FeatureClient } from "@azure/arm-features";
import { MonitorClient } from "@azure/arm-monitor";
import { ManagedServiceIdentityClient } from "@azure/arm-msi";
import { ResourceManagementClient } from "@azure/arm-resources";
import { SubscriptionClient } from "@azure/arm-resources-subscriptions";
import { StorageManagementClient } from "@azure/arm-storage";
import { ReadyAzureSessionProvider } from "../../auth/types";
import { ContainerRegistryManagementClient } from "@azure/arm-containerregistry";
import { ContainerRegistryClient } from "@azure/container-registry";
import { AuthorizationManagementClient } from "@azure/arm-authorization";
import { FeatureClient } from "@azure/arm-features";
import { PagedAsyncIterableIterator } from "@azure/core-paging";
import { getCredential, getEnvironment } from "../../auth/azureAuth";
import { ReadyAzureSessionProvider } from "../../auth/types";
import { Errorable, getErrorMessage } from "./errorable";

export function getSubscriptionClient(sessionProvider: ReadyAzureSessionProvider): SubscriptionClient {
return new SubscriptionClient(getCredential(sessionProvider), { endpoint: getArmEndpoint() });
Expand All @@ -23,14 +24,10 @@ export function getResourceManagementClient(
return new ResourceManagementClient(getCredential(sessionProvider), subscriptionId, { endpoint: getArmEndpoint() });
}

export function getFeatureClient(
sessionProvider: ReadyAzureSessionProvider,
subscriptionId: string,
): FeatureClient {
export function getFeatureClient(sessionProvider: ReadyAzureSessionProvider, subscriptionId: string): FeatureClient {
return new FeatureClient(getCredential(sessionProvider), subscriptionId);
}


export function getAksClient(
sessionProvider: ReadyAzureSessionProvider,
subscriptionId: string,
Expand Down Expand Up @@ -75,6 +72,13 @@ export function getAuthorizationManagementClient(
});
}

export function getManagedServiceIdentityClient(
sessionProvider: ReadyAzureSessionProvider,
subscriptionId: string,
): ManagedServiceIdentityClient {
return new ManagedServiceIdentityClient(getCredential(sessionProvider), subscriptionId);
}

function getArmEndpoint(): string {
return getEnvironment().resourceManagerEndpointUrl;
}
Expand Down
29 changes: 17 additions & 12 deletions src/commands/utils/clusters.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import * as azcs from "@azure/arm-containerservice";
import * as fs from "fs";
import * as yaml from "js-yaml";
import * as path from "path";
import { dirSync } from "tmp";
import { AuthenticationSession, authentication } from "vscode";
import {
API,
APIAvailable,
Expand All @@ -7,22 +13,16 @@ import {
KubectlV1,
extension,
} from "vscode-kubernetes-tools-api";
import { getTokenInfo } from "../../auth/azureAuth";
import { ReadyAzureSessionProvider, TokenInfo } from "../../auth/types";
import { AksClusterTreeNode } from "../../tree/aksClusterTreeItem";
import * as azcs from "@azure/arm-containerservice";
import { Errorable, failed, getErrorMessage, map as errmap, succeeded } from "./errorable";
import { SubscriptionTreeNode, isSubscriptionTreeNode } from "../../tree/subscriptionTreeItem";
import * as yaml from "js-yaml";
import * as fs from "fs";
import * as path from "path";
import { getAksClient } from "./arm";
import { Errorable, map as errmap, failed, getErrorMessage, succeeded } from "./errorable";
import { getKubeloginBinaryPath } from "./helper/kubeloginDownload";
import { longRunning } from "./host";
import { dirSync } from "tmp";
import { ReadyAzureSessionProvider, TokenInfo } from "../../auth/types";
import { AuthenticationSession, authentication } from "vscode";
import { getTokenInfo } from "../../auth/azureAuth";
import { getAksClient } from "./arm";
import { withOptionalTempFile } from "./tempfile";
import { invokeKubectlCommand } from "./kubectl";
import { withOptionalTempFile } from "./tempfile";

export interface KubernetesClusterInfo {
readonly name: string;
Expand Down Expand Up @@ -568,5 +568,10 @@ export async function filterPodName(
}

function isDefinedManagedCluster(cluster: azcs.ManagedCluster): cluster is DefinedManagedCluster {
return cluster.id !== undefined && cluster.name !== undefined && cluster.location !== undefined;
return (
cluster.id !== undefined &&
cluster.name !== undefined &&
cluster.location !== undefined &&
cluster.nodeResourceGroup !== undefined
);
}
49 changes: 49 additions & 0 deletions src/commands/utils/managedServiceIdentity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
FederatedIdentityCredential,
ManagedServiceIdentityClient,
UserAssignedIdentitiesGetResponse,
} from "@azure/arm-msi";
import { Errorable, getErrorMessage } from "./errorable";

export async function getIdentity(
client: ManagedServiceIdentityClient,
resourceGroupName: string,
resourceName: string,
): Promise<Errorable<UserAssignedIdentitiesGetResponse>> {
try {
const identity = await client.userAssignedIdentities.get(resourceGroupName, resourceName);
if (!identity || !identity.principalId) {
return { succeeded: false, error: "Identity does not have a principalId" };
}
return { succeeded: true, result: identity };
} catch (e) {
return { succeeded: false, error: getErrorMessage(e) };
}
}

export async function createFederatedCredential(
client: ManagedServiceIdentityClient,
resourceGroupName: string,
resourceName: string,
federatedCredentialName: string,
issuer: string,
subject: string,
audience: string,
): Promise<Errorable<void>> {
try {
const federatedCredential: FederatedIdentityCredential = {
issuer: issuer,
subject: subject,
audiences: [audience],
};
await client.federatedIdentityCredentials.createOrUpdate(
resourceGroupName,
federatedCredentialName,
resourceName,
federatedCredential,
);
return { succeeded: true, result: undefined };
} catch (e) {
return { succeeded: false, error: getErrorMessage(e) };
}
}
6 changes: 3 additions & 3 deletions src/commands/utils/roleAssignments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
RoleAssignment,
RoleAssignmentCreateParameters,
} from "@azure/arm-authorization";
import { Errorable, failed, getErrorMessage } from "./errorable";
import { listAll } from "./arm";
import { v4 as uuidv4 } from "uuid";
import { listAll } from "./arm";
import { acrProvider, acrResourceName } from "./azureResources";
import { Errorable, failed, getErrorMessage } from "./errorable";

export function getPrincipalRoleAssignmentsForAcr(
client: AuthorizationManagementClient,
Expand Down Expand Up @@ -36,8 +36,8 @@ export async function createRoleAssignment(
subscriptionId: string,
principalId: string,
roleDefinitionName: string,
principalType: PrincipalType,
scope: string,
principalType?: PrincipalType,
): Promise<Errorable<RoleAssignment>> {
const newRoleAssignmentName = createRoleAssignmentName();
const roleDefinitionId = `/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${roleDefinitionName}`;
Expand Down
32 changes: 16 additions & 16 deletions src/panels/AttachAcrToClusterPanel.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import { RoleAssignment } from "@azure/arm-authorization";
import { Uri, window } from "vscode";
import { BasePanel, PanelDataProvider } from "./BasePanel";
import { MessageHandler, MessageSink } from "../webview-contract/messaging";
import { TelemetryDefinition } from "../webview-contract/webviewTypes";
import { ReadyAzureSessionProvider } from "../auth/types";
import { getAuthorizationManagementClient } from "../commands/utils/arm";
import { acrResourceType, clusterResourceType, getResources } from "../commands/utils/azureResources";
import { getManagedCluster } from "../commands/utils/clusters";
import { Errorable, failed, getErrorMessage } from "../commands/utils/errorable";
import {
createRoleAssignment,
deleteRoleAssignment,
getPrincipalRoleAssignmentsForAcr,
getScopeForAcr,
} from "../commands/utils/roleAssignments";
import { SelectionType, getSubscriptions } from "../commands/utils/subscriptions";
import { MessageHandler, MessageSink } from "../webview-contract/messaging";
import {
AcrKey,
ClusterKey,
Expand All @@ -14,18 +24,8 @@ import {
ToWebViewMsgDef,
acrPullRoleDefinitionName,
} from "../webview-contract/webviewDefinitions/attachAcrToCluster";
import { Errorable, failed, getErrorMessage } from "../commands/utils/errorable";
import { SelectionType, getSubscriptions } from "../commands/utils/subscriptions";
import { acrResourceType, clusterResourceType, getResources } from "../commands/utils/azureResources";
import { getAuthorizationManagementClient } from "../commands/utils/arm";
import { RoleAssignment } from "@azure/arm-authorization";
import {
createRoleAssignment,
deleteRoleAssignment,
getPrincipalRoleAssignmentsForAcr,
getScopeForAcr,
} from "../commands/utils/roleAssignments";
import { getManagedCluster } from "../commands/utils/clusters";
import { TelemetryDefinition } from "../webview-contract/webviewTypes";
import { BasePanel, PanelDataProvider } from "./BasePanel";

export class AttachAcrToClusterPanel extends BasePanel<"attachAcrToCluster"> {
constructor(extensionUri: Uri) {
Expand Down Expand Up @@ -179,8 +179,8 @@ export class AttachAcrToClusterDataProvider implements PanelDataProvider<"attach
acrKey.subscriptionId,
principalId.result,
acrPullRoleDefinitionName,
"ServicePrincipal",
scope,
"ServicePrincipal",
);

if (failed(roleAssignment)) {
Expand Down
Loading

0 comments on commit 0c016fd

Please sign in to comment.