본문 바로가기
WIKI 기술 지식 베이스

새 Auth 서비스로 마이그레이션

원문 보기 위키 갱신

새 Auth 서비스로 마이그레이션 (Migrating to New Auth Services)

Backstage 백엔드 시스템의 auth 서비스는 1.24 릴리스에서 재작업되었습니다. 무엇보다도 Backstage 백엔드에 대한 기본 보호가 도입되어 contrib의 authenticate-api-requests.md 가이드를 대체합니다.

출처: 문서

본문

Backstage 백엔드 시스템의 auth 서비스는 1.24 릴리스에서 재작업되었습니다. 무엇보다도 Backstage 백엔드에 대한 기본 보호가 도입되어 contrib의 authenticate-api-requests.md 가이드를 대체합니다. 이 가이드는 기존 백엔드 설정뿐 아니라 백엔드 플러그인과 모듈을 새 auth 서비스를 사용하도록 마이그레이션하는 데 도움을 줍니다.

새 auth 서비스를 수반하는 가장 영향이 큰 변경은 새 백엔드 시스템에서 실행되는 모든 플러그인의 기본 동작으로, 사용자나 서비스로 인증되지 않은 모든 요청을 차단한다는 것입니다. 이를 기본 인증 정책(default auth policy)이라고도 합니다. 이는 이 업데이트의 일부로 도입된 유일한 파괴적인 프로덕션 변경이며, 백엔드 설치와 플러그인 모두에서 조치가 필요할 수 있습니다. 자세한 내용은 아래 개별 섹션을 참조하세요.

백엔드 마이그레이션 (Backend migration)

이 새 서비스를 사용하려면 백엔드가 새 백엔드 시스템을 사용하고 있어야 합니다. 백엔드가 이전 시스템을 실행 중이라면 먼저 새 시스템으로 마이그레이션해야 합니다.

백엔드에 authenticate-api-requests.md가 설치되어 있다면 일반적으로 제거하고 대신 새 auth 서비스를 사용해야 합니다. 아직 그 변경을 하지 않으면서 Backstage 최신 릴리스로 업그레이드하고 싶다면 그것을 그대로 두고 다음 섹션에서 설명하는 대로 기본 인증 정책을 비활성화할 수도 있습니다.

기본 인증 정책 비활성화 (Disabling the default auth policy)

요청 인증을 기본적으로 적용하고 싶지 않다면 기본 인증 정책을 비활성화할 수 있습니다. 이는 다음 구성으로 수행합니다.

backend:  auth:    dangerouslyDisableDefaultAuthPolicy: true

이 기능은 향후 릴리스에서 제거될 것임을 유의하세요. 가능한 한 빨리 새 auth 서비스를 사용하도록 마이그레이션해야 하며, 그렇지 않으면 토큰 발급용 자체 서비스를 지원해야 합니다.

간단히 말해 이렇게 하면 자격 증명을 포함하지 않더라도 백엔드의 플러그인에 요청이 통과될 수 있습니다. 다만 요청은 여전히 인증되지 않은 것으로 취급되며, 모든 플러그인 엔드포인트가 이를 허용하지 않을 수 있습니다. 이 구성의 영향에 대한 자세한 내용은 auth service 문서를 참조하세요.

백엔드 마이그레이션 (Migrating the backend)

기본 인증 정책을 유지하고 싶다면 백엔드 자체를 마이그레이션하기 위해 약간의 조치가 필요합니다. 새 auth 서비스에 필요할 수 있는 업데이트를 받기 위해 모든 플러그인을 최신 버전으로 업그레이드하세요. 내부 플러그인이나 모듈이 있다면 아래 플러그인 마이그레이션 섹션을 참조하세요.

기본 인증 정책이 적용된 상태에서 이제 로컬 개발 중에도 백엔드에 대한 요청이 인증되었는지 확인해야 합니다. 이미 로컬 개발에 auth 공급자를 사용하는 설정이 있다면 계속 사용할 수 있습니다. 하지만 로컬 개발에 'guest' 접근에 의존한다면 auth 백엔드에 새 guest 공급자 모듈을 설치할 것을 권장합니다.

yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-guest-provider

백엔드에 추가하세요.

packages/backend/src/index.ts

backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));

마지막으로 개발 구성에 다음을 추가하세요.

auth:  providers:    guest: {}

guest 공급자를 로컬 개발에서만 활성화하고 프로덕션에서는 활성화하지 않도록 하세요. 기본적으로 프로덕션에서 활성화를 거부하지만, 아예 피하는 것이 가장 좋습니다. 별도의 개발 구성이 없다면 프로덕션 구성에 다음을 추가하세요.

auth:  providers:    guest: null

비개발 환경에서도 guest 로그인을 활성화하려면 이 구성 스니펫을 사용할 수 있습니다.

auth:  providers:    guest:      dangerouslyAllowOutsideDevelopment: true

이것이 guest 인증에 필요한 전부입니다! @backstage/core-components의 기본 SignInPage는 guest 공급자가 활성화되어 있으면 감지하여 사용합니다.

기본 인증 정책이 새 백엔드 시스템에서 실행되는 모든 플러그인에 적용되므로 개별 플러그인이 보호되는지 여부를 걱정할 필요가 없습니다. 아직 마이그레이션되지 않은 플러그인의 영향은 인증되지 않은 요청을 허용해야 할 엔드포인트가 있지만 지금은 기본 인증 정책에 의해 차단될 수 있다는 것입니다. 개별 플러그인에 대해 일시적으로 해결하려면 http router 서비스를 통해 필요한 정책을 추가하는 플러그인용 모듈을 설치할 수 있습니다.

사용자 지정 identity 또는 token manager 서비스 구현이 있다면 @backstage/backend-common의 createLegacyAuthAdapters 헬퍼를 사용해 새 auth 서비스에 맞게 조정할 수 있습니다.

플러그인 & 모듈 마이그레이션 (Plugin & Module migration)

이 가이드 부분은 백엔드 플러그인 또는 모듈을 새 auth API를 사용하도록 마이그레이션하는 데 도움을 줍니다. 두 가지 주요 섹션으로 나뉩니다. 첫 번째는 새 백엔드 시스템을 위해 플러그인에 필요한 auth 정책을 추가하는 것이고, 두 번째는 새 auth 서비스를 사용하도록 마이그레이션하는 것입니다. 첫 번째 단계가 더 긴급하며 새 백엔드 시스템에서 플러그인이 계속 기능하려면 필요할 수 있습니다. 두 번째 단계는 덜 긴급하며 기존 auth 서비스에 대한 지원이 제거될 때까지는 필요하지 않습니다.

auth 정책 추가 (Adding auth policies)

플러그인이 새 백엔드 시스템을 지원한다면 기본 인증 정책에 예외를 추가해야 할 수 있습니다. 플러그인이 인증되지 않은 요청이나 사용자 쿠키로 인증된 요청을 수락해야 한다면 그에 대한 정책을 추가해야 합니다. 이는 httpRouter 서비스를 사용해 수행합니다. 예를 들어 다음은 /health 엔드포인트에 인증되지 않은 요청을 허용합니다.

export default createBackendPlugin({  pluginId: 'example',  register(env) {    env.registerInit({      deps: {        config: coreServices.rootConfig,        logger: coreServices.logger,        httpRouter: coreServices.httpRouter,        auth: coreServices.auth,        httpAuth: coreServices.httpAuth,      },      async init({ config, logger, httpRouter, auth, httpAuth }) {        httpRouter.use(await createRouter({ config, logger, auth, httpAuth }));        httpRouter.addAuthPolicy({          path: '/health',          allow: 'unauthenticated',        });      },    });  },});

새 auth 서비스 사용 (Using the new auth services)

이 섹션의 목표는 플러그인 내부에서 기존 identity 및 token manager 서비스의 사용을 완전히 제거하고 대신 새 auth 및 http auth 서비스를 사용하는 것입니다. 다만 플러그인은 기존 사용자의 설정을 깨지 않도록 플러그인 환경에서 identity 및 tokenManager 서비스를 선택적 의존성으로 계속 받아들일 수 있습니다.

플러그인이 현재 identity 또는 tokenManager 서비스에 의존하지 않거나 내부적으로 DefaultIdentityClient를 사용한다면 이 단계는 필요하지 않으며 추가 조치는 필요 없습니다.

이 가이드는 플러그인이 이전 백엔드 시스템의 외부 API로 createRouter 패턴을 사용한다고 가정합니다. 다른 및/또는 추가 외부 API 표면이 있다면 같은 방식으로 처리해야 하지만, 이 예시를 구현에 맞게 조정해야 할 수 있습니다.

새 백엔드 시스템에서 의존성 업데이트 (Updating dependencies in the new backend system)

플러그인이 새 백엔드 시스템을 지원한다면 마이그레이션의 첫 단계는 새 auth 서비스를 사용하도록 하는 것입니다. 당분간은 AuthService와 HttpAuthService를 모두 추가하겠지만, 결국 둘 중 하나만 필요할 수도 있으며 그 경우 다른 하나를 제거할 수 있습니다.

export default createBackendPlugin({  pluginId: 'example',  register(env) {    env.registerInit({      deps: {        config: coreServices.rootConfig,        logger: coreServices.logger,        discovery: coreServices.discovery,        httpRouter: coreServices.httpRouter,        identity: coreServices.identity,        tokenManager: coreServices.tokenManager,        auth: coreServices.auth,        httpAuth: coreServices.httpAuth,      },      async init({        config,        logger,        discovery,        httpRouter,        identity,        tokenManager,        auth,        httpAuth,      }) {        const router = await createRouter({          config,          logger,          discovery,          identity,          tokenManager,          auth,          httpAuth,        });        httpRouter.use();      },    });  },});

플러그인이 현재 identity 또는 tokenManager 서비스에 의존하지 않는다면 걱정하지 마세요. 무시할 수 있습니다. 다만 플러그인이 discovery 서비스에 아직 의존하지 않는다면 필수 의존성으로 추가해야 합니다. 도입할 호환성 계층에 필요하기 때문입니다.

createRouter에서 새 auth 서비스 사용 가능하게 하기 (Making the new auth services available in createRouter)

새 auth 서비스를 이전 버전과 호환되는 방식으로 플러그인 구현에 사용 가능하게 하기 위해 @backstage/backend-common의 createLegacyAuthAdapters 헬퍼를 사용합니다. 이 헬퍼는 이전 및 새 auth 서비스를 모두 받아들이고 새 서비스에 대한 구현을 반환합니다. 새 서비스에 대한 구현이 제공되면 직접 전달하며, 새 백엔드 시스템에 대해 원하는 것입니다. 새 서비스가 제공되지 않으면 이전 서비스를 사용해 폴백 구현을 만들고, 이전 서비스도 사용할 수 없으면 이전 서비스의 기본 구현으로 폴백합니다.

실제로 이 변경을 createRouter 함수에 적용하면 다음과 같을 수 있습니다.

export interface RouterOptions {  config: RootConfigService;  logger: LoggerService;  discovery: DiscoveryService;  identity?: IdentityService;  auth?: AuthService;  httpAuth?: HttpAuthService;}export function createRouter(options: RouterOptions) {  const { auth, httpAuth } = createLegacyAuthAdapters(options);  // ... the rest of the implementation}

createRouter 함수가 이미 identity 또는 tokenManager 서비스를 받아들이지 않는다면 추가하지 말아야 합니다. 마찬가지로 플러그인이 둘 중 하나에 사용하는 기본 구현이 있다면 그 구현을 createLegacyAuthAdapters에 전달해야 합니다. 이 두 제약 조건 모두 플러그인이 이전과 동일하게 계속 동작하도록 보장합니다.

앞서 언급했듯이 구현에서 auth와 httpAuth를 모두 필요로 하지 않을 수도 있습니다. 그렇다면 라우터 옵션에서 사용하지 않는 것을 제거해야 합니다.

이전 auth 서비스 호출 대체 (Replacing old auth service calls)

auth 및 httpAuth 서비스가 플러그인 구현에서 사용 가능해지면 남은 것은 기존 identity 및 tokenManager 서비스의 사용을 대체하는 것입니다. 이 섹션에서는 기존 서비스의 가장 흔한 사용법과 새 서비스를 사용하도록 마이그레이션하는 방법을 살펴보고 설명합니다.

예시 1: 독립형 서비스 간 요청 만들기 (Example 1: Making a standalone service-to-service request)

요청 경로에 있지 않거나 상승된 권한이 필요한 서비스 간 요청에 대한 새 서비스 토큰을 생성하려면 이전에는 다음을 사용했을 것입니다.

const { token } = await tokenManager.getToken();

새 auth 서비스를 사용한 동등한 코드는 다음과 같습니다.

const { token } = await auth.getPluginRequestToken({  onBehalfOf: await auth.getOwnServiceCredentials(),  targetPluginId: '<plugin-id>', // e.g. 'catalog'});

onBehalfOf 옵션은 요청에 사용하려는 자격 증명을 제공합니다. 여기서는 플러그인의 자체 자격 증명을 사용하지만, 다른 곳에서는 수신 요청의 자격 증명을 전달하는 데도 사용되는 것을 볼 수 있습니다.

targetPluginId는 서비스 간 인증을 더 세밀하게 제어할 수 있게 해주는 새 요구 사항입니다. 서비스 간 요청에 대한 새 토큰을 생성할 때 이제 요청을 보낼 플러그인의 ID를 지정해야 합니다.

예시 2: 수신 요청에서 자격 증명 전달 (Example 2: Forwarding credentials from an incoming request)

수신 요청에서 자격 증명을 읽는 것은 일반적으로 다음과 같았습니다.

router.get('/example/:entityRef', async (req, _res) => {  const token = getBearerTokenFromAuthorizationHeader(    req.header('authorization'),  );  // Some followup call using the token, for example using the catalog client  const entity = await catalogClient.getEntityByRef(req.params.entityRef, {    token,  });  // Or forwarding the token to evaluate permissions  await permissions.authorize(    [{ permission: examplePermission, resourceRef: entityRef }],    { token },  );});

새 auth 서비스는 업스트림 요청에서 사용자 및 서비스 토큰을 직접 전달하는 것을 피하기 위해 이 과정에 의도적으로 추가 단계를 넣었습니다. 이제 수신 요청에서 먼저 자격 증명을 추출한 다음 그 자격 증명을 사용해 업스트림 요청에 대한 새 토큰을 생성합니다.

새 auth 서비스에서 위 예시는 이제 다음과 같습니다.

router.get('/example/:entityRef', async (req, _res) => {  const credentials = await httpAuth.credentials(req);  // The catalog client only accepts tokens right now, it will be updated  // to accept credentials directly in the future.  // For now we will need to issue a new token to pass to the catalog client.  const { token } = await auth.getPluginRequestToken({    onBehalfOf: credentials,    targetPluginId: 'catalog',  });  const entity = await catalogClient.getEntityByRef(req.params.entityRef, {    token,  });  // The permissions service accepts credentials directly  await permissions.authorize(    [{ permission: examplePermission, resourceRef: entityRef }],    { credentials },  );});

위 permissions 호출이 작동하려면 플러그인이 PermissionEvaluator가 아니라 @backstage/backend-plugin-api의 PermissionsService에 의존하도록 업데이트해야 합니다.

일반적인 패턴으로 플러그인을 리팩터링하여 BackstageCredentials 객체를 가능한 한 멀리 전달하고, 사용 직전에만 토큰을 생성하도록 하는 것이 좋습니다.

예시 3: 요청에서 사용자 신원 가져오기 (Example 3: Getting the user identity from a request)

수신 요청에서 사용자 신원을 가져오려면 이전에는 identity 서비스를 사용했을 것입니다.

router.get('/example/by-user', async (req, _res) => {  const user = await identity.getIdentity({ request: req });  if (!user) {    throw new AuthenticationError();  }  console.log(`User ${user.identity.userEntityRef} is making a request`);});

새 auth 서비스를 사용한 동등한 코드는 다음과 같습니다.

router.get('/example/by-user', async (req, _res) => {  const credentials = await httpAuth.credentials(req, { allow: ['user'] });  console.log(    `User ${credentials.principal.userEntityRef} is making a request`,  );});

위 코드에서 credentials 호출의 allow 옵션은 허용되는 사용자 자격 증명을 좁히는 데 사용됩니다. 수신 요청이 사용자로 인증되지 않으면 credentials 호출이 오류를 던집니다.

기존 코드가 인증된 사용자를 요구하지 않고 사용 가능할 때만 사용한다면 allow: ['user', 'service', 'none']을 credentials 호출에 전달한 다음 credentials.principal.type을 확인할 수 있습니다.

더 알아보기 (Learn more)