Kubernetes 인증 전략
Kubernetes 인증 전략(Auth Strategy)은 Kubernetes 클러스터에 인증하기 위해 서버 측에서 실행되는 인증 단계를 지정해요. 또한 클라이언트 측 인증 프로바이더가 요구하는 경우, Kubernetes 클러스터에 대한 어떤 인증 메타데이터가 프론트엔드로 반환되는지도 정의해요.
출처: 문서
본문
Kubernetes 인증 전략(Auth Strategy)은 Kubernetes 클러스터에 인증하기 위해 서버 측에서 실행되는 인증 단계를 지정해요. 또한 클라이언트 측 인증 프로바이더가 요구하는 경우, Kubernetes 클러스터에 대한 어떤 인증 메타데이터가 프론트엔드로 반환되는지도 정의해요.
맥락(Context)
Backstage는 기본적으로 Kubernetes 클러스터에 대한 인증 과정을 쉽게 하기 위한 몇 가지 Kubernetes 인증 프로바이더를 포함해요. 다음이 포함돼요.
-
localKubectlProxy나serviceAccount같은서버 측 프로바이더(Server Side Providers). 여기서는 같은 Kubernetes 권한 집합이 Backstage 사용자와 플러그인 사이에서 공유되고 부여돼요. -
aks나oidc같은클라이언트 측 프로바이더(Client Side Providers). 여기서는 사용자가 클러스터와 함께 인증되며, 그 특정 사용자에게 부여된 Kubernetes 권한만 얻어요.
서버 측 프로바이더와 클라이언트 측 프로바이더가 있지만, 클러스터로 인증하려면 양쪽 모두에서 코드가 필요해요. 어쩌면 그중 하나가 대부분의 작업을 하겠지만, 일반적으로 Kubernetes 클러스터에 인증하는 모든 단계가 항상 전적으로 서버 측이나 클라이언트 측에서만 실행되지는 않아요. Kubernetes 인증 흐름은 인증 과정을 클라이언트 측 단계와 서버 측 단계 사이에 나누어야 할 수도 있어요.
AuthenticationStrategy 인터페이스
AuthenticationStrategy 인터페이스는 Kubernetes 클러스터에 인증하기 위해 서버 측에서 실행되는 단계를 정의해요. 이것은 클라이언트 측의 해당 단계를 정의하는 KubernetesAuthProvider 인터페이스와 비슷해요.
plugins/kubernetes-node/src/types/types.ts
export interface AuthenticationStrategy {
getCredential(
clusterDetails: ClusterDetails,
authConfig: KubernetesRequestAuth,
): Promise<KubernetesCredential>;
presentAuthMetadata(authMetadata: AuthMetadata): AuthMetadata;
validateCluster(authMetadata: AuthMetadata): Error[];
}
AuthenticationStrategy 인터페이스는 다음 시그니처를 정의해요.
getCredential: Kubernetes 클러스터에 인증하기 위해 서버 측에서 필요한 단계를 실행해요.clusterDetails매개변수로 클러스터 정보를 받고,authConfig매개변수로 클라이언트 측에서 제공된 인증 데이터를 받아요.presentAuthMetadata: Kubernetes 클러스터 구성은 특정 인증 흐름에 특화된 추가 메타데이터(AWS 클러스터처럼)를 포함할 수 있어요.presentAuthMetadata메서드는 그 메타데이터를 받아 클라이언트 측 인증 과정에서 프론트엔드가 요구할 수 있는 정보를 필터링/추가해요. 프론트엔드는/clusters엔드포인트를 통해 이 정보를 얻어요.validateCluster: 전략이 잘못된 메타데이터를 가진 클러스터를 거부할 수 있게 해줘요. 현재 이 메서드는 app-config에서 클러스터를 읽을 때만 호출돼요.
KubernetesCredential 타입
강조할 것은 AuthenticationStrategy의 getCredential 메서드가 Kubernetes 클러스터로 인증하는 단일 방법을 나타내는 KubernetesCredential 객체를 반환한다는 점이에요. 이 값은 다음과 같을 수 있어요.
- Bearer 토큰
- x509 클라이언트 인증서와 키
- 익명(anonymous) 인증
plugins/kubernetes-node/src/types/types.ts
export type KubernetesCredential =
| { type: 'bearer token'; token: string }
| { type: 'x509 client certificate'; cert: string; key: string }
| { type: 'anonymous' };
AuthenticationStrategy 예시
AksStrategy
일부 Kubernetes 인증 전략은 아주 단순해요. 인증 과정이 KubernetesAuthProvider에 의해 클라이언트 측에서 실행되었기 때문이에요. 그래서 AksStrategy나 GoogleStrategy 같은 인증 전략은 해당 KubernetesAuthProvider가 클라이언트 측에서 반환한 정보를 매핑만 해요.
plugins/kubernetes-backend/src/auth/AksStrategy.ts
export class AksStrategy implements AuthenticationStrategy {
public async getCredential(
_: ClusterDetails,
requestAuth: KubernetesRequestAuth,
): Promise<KubernetesCredential> {
const token = requestAuth.aks;
return token
? { type: 'bearer token', token: token as string }
: { type: 'anonymous' };
}
public validateCluster(): Error[] {
return [];
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
}
AksStrategy는 아주 단순하며, AksKubernetesAuthProvider.ts가 클라이언트 측 인증 흐름에서 얻을 수 있었던 토큰을 매핑만 해요.
AwsIamStrategy
또 다른 AuthenticationStrategy는 AwsIamStrategy예요. 이것은 일부 AWS API를 사용해 Kubernetes 토큰을 얻기 때문에 AksStrategy보다 더 복잡해요.
plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts
export class AwsIamStrategy implements AuthenticationStrategy {
// ... code ...
public async getCredential(
clusterDetails: ClusterDetails,
): Promise<KubernetesCredential> {
return {
type: 'bearer token',
token: await this.getBearerToken(
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_CLUSTER_ID] ??
clusterDetails.name,
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE],
clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID],
),
};
}
private async getBearerToken(
clusterId: string,
assumeRole?: string,
externalId?: string,
): Promise<string> {
// ... code ...
const request = await signer.presign(
{
headers: {
host: `sts.${region}.amazonaws.com`,
'x-k8s-aws-id': clusterId,
},
hostname: `sts.${region}.amazonaws.com`,
method: 'GET',
path: '/',
protocol: 'https:',
query: {
Action: 'GetCallerIdentity',
Version: '2011-06-15',
},
},
{ expiresIn: 0 },
);
// ... code ...
}
public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata {
return {};
}
public validateCluster(): Error[] {
return [];
}
}
사용자 지정 AuthStrategy
때로는 Backstage가 기본적으로 지원하지 않는 Kubernetes 클러스터에 인증하는 새 방법을 추가해야 해요. 이것이 통합자가 KubernetesBuilder의 addAuthStrategy 메서드나 AuthStrategyExtensionPoint를 통해 자신의 Kubernetes 인증 전략을 가져올 수 있는 방법이에요. 다음 섹션에서 Kubernetes 클러스터용 인증 서비스인 Pinniped를 위한 새 AuthStrategy를 소개할 거예요.
백엔드 시스템의 사용자 지정 Pinniped 인증 전략
새 AuthStrategy를 추가하려면 Kubernetes-Backend 플러그인을 확장하는 새 Pinniped 백엔드 모듈을 만들어야 해요. Pinniped 모듈은 플러그인이 등록한 확장 포인트를 통해 Kubernetes-Backend 플러그인과 상호작용할 거예요. Kubernetes-Backend 플러그인은 kubernetesObjectsProvider, kubernetesClusterSupplier, kubernetesFetcher, kubernetesServiceLocator, kubernetesAuthStrategy 같은 여러 확장 포인트를 등록해요.
이 가이드는 이미 Kubernetes 플러그인을 설치했다고 가정한다는 점에 유의하세요.
백엔드 모듈을 만들려면 yarn new를 실행하고 backend-module을 선택하세요. 그런 다음 입력하세요.
? What do you want to create? backend-module - A new backend module
? Enter the ID of the plugin [required] kubernetes
? Enter the ID of the module [required] pinniped
이것은 plugins/kubernetes-backend-module-pinniped에 새 패키지를 만들 거예요. 또한 @backstage/plugin-kubernetes-node와 @backstage/plugin-kubernetes-common 의존성이 필요해요. @backstage/plugin-kubernetes-node은 kubernetesAuthStrategyExtensionPoint와 Pinniped Helper 클래스를 담고 있어요.
Backstage 루트 디렉터리에서
yarn --cwd plugins/kubernetes-backend-module-pinniped add @backstage/plugin-kubernetes-node
yarn --cwd plugins/kubernetes-backend-module-pinniped add @backstage/plugin-kubernetes-common
AuthenticationStrategy 인터페이스를 구현할 Pinniped 인증 전략을 담을 새 파일을 만들어 보겠어요.
plugins/kubernetes-backend-module-pinniped/src/PinnipedStrategy.ts
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
import { LoggerService } from '@backstage/backend-plugin-api';
import {
AuthMetadata,
AuthenticationStrategy,
ClusterDetails,
KubernetesCredential,
PinnipedClientCerts,
PinnipedHelper,
PinnipedParameters,
} from '@backstage/plugin-kubernetes-node';
import { JsonObject } from '@backstage/types';
export class PinnipedStrategy implements AuthenticationStrategy {
private pinnipedHelper: PinnipedHelper;
constructor(private readonly logger: LoggerService) {
this.pinnipedHelper = new PinnipedHelper(logger);
}
public async getCredential(
clusterDetails: ClusterDetails,
requestAuth: KubernetesRequestAuth,
): Promise<KubernetesCredential> {
const params: PinnipedParameters = {
token:
((requestAuth.pinniped as JsonObject)?.clusteridtoken as string) || '',
authenticator: {
apiGroup: 'authentication.concierge.pinniped.dev',
kind: 'JWTAuthenticator',
name: 'supervisor',
},
tokenCredentialRequest: {
apiGroup: 'login.concierge.pinniped.dev/v1alpha1',
},
};
const x509Data: PinnipedClientCerts =
await this.pinnipedHelper.tokenCredentialRequest(clusterDetails, params);
return {
type: 'x509 client certificate',
cert: x509Data.cert,
key: x509Data.key,
};
}
public validateCluster(): Error[] {
return [];
}
presentAuthMetadata: (authMetadata: AuthMetadata): AuthMetadata => {
return {
audience: authMetadata['kubernetes.io/x-pinniped-audience'],
};
};
}
PinnipedStrategy는 AuthenticationStrategy 인터페이스를 구현하며, PinnipedHelper 클래스를 사용해 clusterIdToken(사용자 지정 Pinniped 클라이언트 측 KubernetesAuthProvider가 만든)을 Kubernetes 클러스터를 소비할 수 있게 해주는 x509 인증서로 교환해요. 또한 presentAuthMetadata를 통해 audience 값을 프론트엔드에 반환해요.
PinnipedHelper 클래스는 토큰을 교환하는 데만 도움을 주며, 전략이 도입할 수 있는 캐시 레이어 없이 동작한다는 점에 유의하세요.
마지막으로 kubernetesAuthStrategyExtensionPoint를 사용해 새 PinnipedStrategy를 등록할 수 있어요.
plugins/kubernetes-backend-module-pinniped/src/module.ts
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { kubernetesAuthStrategyExtensionPoint } from '@backstage/plugin-kubernetes-node';
import { PinnipedStrategy } from './PinnipedStrategy';
export const kubernetesModulePinniped = createBackendModule({
pluginId: 'kubernetes',
moduleId: 'pinniped',
register(reg) {
reg.registerInit({
deps: {
logger: coreServices.logger,
authStrategy: kubernetesAuthStrategyExtensionPoint,
},
async init({ logger, authStrategy }) {
authStrategy.addAuthStrategy('pinniped', new PinnipedStrategy(logger));
},
});
},
});
기존 백엔드 시스템의 사용자 지정 Pinniped 인증 전략
새 AuthStrategy를 추가하려면 KubernetesBuilder의 addAuthStrategy 메서드를 사용할 수 있어요. 이전 섹션에서 만든 PinnipedStrategy를 재사용할 거예요. 그래서 Kubernetes 백엔드 플러그인을 설정할 때 새 전략을 추가할 수 있어요.
packages/backend/src/plugins/kubernetes.ts
import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { CatalogClient } from '@backstage/catalog-client';
import { AuthenticationStrategy } from '@backstage/plugin-kubernetes-node';
import { PinnipedStrategy } from '@internal/plugin-kubernetes-backend-module-pinniped';
export default async function createPlugin(
env: PluginEnvironment,
): Promise<Router> {
const catalogApi = new CatalogClient({ discoveryApi: env.discovery });
const pinnipedStrategy: AuthenticationStrategy = new PinnipedStrategy(
env.logger,
);
const { router } = await KubernetesBuilder.createBuilder({
logger: env.logger,
config: env.config,
catalogApi,
permissions: env.permissions,
})
.addAuthStrategy('pinniped', pinnipedStrategy)
.build();
return router;
}