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

3. 리소스 권한 검사 추가

원문 보기 위키 갱신

특정 리소스에 대해 업데이트(또는 다른 작업)를 수행할 때 권한 프레임워크는 결정이 리소스 자체의 특성에 기반하도록 할 수 있어요. 이는 예를 들어 리소스를 소유한 사용자에게는 작업을 허용하고 그 외에는 거부하는 정책을 작성할 수 있다는 뜻이에요.

출처: 문서

본문

특정 리소스에 대해 업데이트(또는 다른 작업)를 수행할 때 권한 프레임워크는 결정이 리소스 자체의 특성에 기반하도록 할 수 있어요. 이는 예를 들어 리소스를 소유한 사용자에게는 작업을 허용하고 그 외에는 거부하는 정책을 작성할 수 있다는 뜻이에요.

업데이트 권한 만들기

이전 섹션의 plugins/todo-list-common/src/permissions.ts 파일에 새 권한을 추가해 보겠어요.

plugins/todo-list-common/src/permissions.ts

import { createPermission } from '@backstage/plugin-permission-common';export const TODO_LIST_RESOURCE_TYPE = 'todo-item';export const todoListCreatePermission = createPermission({  name: 'todo.list.create',  attributes: { action: 'create' },});export const todoListUpdatePermission = createPermission({  name: 'todo.list.update',  attributes: { action: 'update' },  resourceType: TODO_LIST_RESOURCE_TYPE,});export const todoListPermissions = [todoListCreatePermission];export const todoListPermissions = [  todoListCreatePermission,  todoListUpdatePermission,];

todoListCreatePermission과 달리 todoListUpdatePermission 권한에 resourceType 필드가 포함되어 있음을 주목하세요. 이 필드는 권한 프레임워크에 이 권한이 'todo-item' 타입의 리소스 맥락에서 인가되도록 의도되었음을 알려줘요. 각 리소스 타입에 대해 같은 값을 일관되게 사용하는 한 리소스 타입으로 원하는 문자열을 사용할 수 있어요.

업데이트 권한에 대한 인가 설정

먼저 plugins/todo-list-backend/src/plugin.ts를 편집해 새 권한을 플러그인에 추가해 보겠어요.

plugins/todo-list-backend/src/plugin.ts

import { todoListCreatePermission } from '@internal/plugin-todo-list-common';import {  todoListCreatePermission,  todoListUpdatePermission,} from '@internal/plugin-todo-list-common';// ...permissionsRegistry.addPermissions([todoListCreatePermission]);permissionsRegistry.addPermissions([  todoListCreatePermission,  todoListUpdatePermission,]);// ...

그럼 이전 섹션에서 했던 것과 같은 방식으로 plugins/todo-list-backend/src/service/router.ts를 편집해 보겠어요.

plugins/todo-list-backend/src/service/router.ts

import { todoListCreatePermission } from '@internal/plugin-todo-list-common';import {  todoListCreatePermission,  todoListUpdatePermission,} from '@internal/plugin-todo-list-common';router.put('/todos', async (req, res) => {  const credentials = await httpAuth.credentials(req, { allow: ['user'] });  if (!isTodoUpdateRequest(req.body)) {    throw new InputError('Invalid payload');  }  const decision = (    await permissions.authorize(      [{ permission: todoListUpdatePermission, resourceRef: req.body.id }],      { credentials },    )  )[0];  if (decision.result !== AuthorizeResult.ALLOW) {    throw new NotAllowedError('Unauthorized');  }  res.json(update(req.body));});

중요: todo 항목의 id를 값으로 하는 추가 resourceRef 필드를 전달하고 있음을 주목하세요.

이를 통해 리소스 특성에 기반한 결정이 가능해지지만, 정책 작성자는 권한 정책 안에서 resource ref에 접근할 수 없다는 점을 알아두는 것이 중요해요. 대신 정책은 조건부 결정을 반환하며, 우리는 이제 그것을 플러그인에서 지원해야 해요.

조건부 결정 지원 추가

누락된 모듈을 설치하세요.

$ yarn workspace @internal/plugin-todo-list-backend add zod@4

새 plugins/todo-list-backend/src/service/rules.ts 파일을 만들고 다음 코드를 추가하세요.

plugins/todo-list-backend/src/service/rules.ts

import {  createPermissionResourceRef,  createPermissionRule,} from '@backstage/plugin-permission-node';import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';import * as z from 'zod';import { Todo, TodoFilter } from './todos';export const todoListPermissionResourceRef = createPermissionResourceRef<  Todo,  TodoFilter>().with({  pluginId: 'todolist',  resourceType: TODO_LIST_RESOURCE_TYPE,});export const isOwner = createPermissionRule({  name: 'IS_OWNER',  description: 'Should allow only if the todo belongs to the user',  resourceRef: todoListPermissionResourceRef,  paramsSchema: z.object({    userId: z.string().describe('User ID to match on the resource'),  }),  apply: (resource: Todo, { userId }) => {    return resource.author === userId;  },  toQuery: ({ userId }) => {    return {      property: 'author',      values: [userId],    };  },});export const rules = { isOwner };

todoListPermissionResourceRef는 리소스 타입과 관련된 타입과 상수를 캡슐화하는 유틸리티예요. 이 리소스에 대해 생성된 모든 규칙에서 리소스와 쿼리 타입이 일관되도록 보장해요.

참고

Backstage 통합자가 정의한 사용자 지정 규칙을 지원하려면, 백엔드 패키지에서 todoListPermissionResourceRef를 내보내야 해요. 서드파티 모듈 생성을 활성화하려면 *-node 패키지에서 내보내야 해요.

우리는 새 isOwner 규칙을 만들었어요. 이 규칙은 resourceRef가 붙은 인가 요청에 대한 응답으로 조건부 응답이 반환될 때마다 권한 프레임워크가 자동으로 사용할 거예요. 구체적으로 apply 함수는 전달된 리소스가 인가되어야 하는지 이해하는 데 사용돼요.

지금은 toQuery 함수를 건너뛰고, 다음 섹션에서 다시 돌아올 거예요.

이제 PermissionsRegistryService를 통해 새 리소스 타입을 권한 시스템에 추가해 보겠어요. 다음을 제공해야 해요.

  • getResources: authorize에 전달될 것으로 기대하는 것과 같은 형식의 resourceRefs 배열을 받아 해당 리소스 배열을 반환하는 함수.
  • resourceType: 위 권한 규칙에서 사용한 것과 같은 값.
  • permissions: 플러그인이 받아들이는 권한 목록.
  • rules: 조건부 결정에서 지원하려는 모든 권한 규칙의 배열.

plugins/todo-list-backend/src/plugin.ts

// ...import {  coreServices,  createBackendPlugin,} from '@backstage/backend-plugin-api';import { createRouter } from './service/router';import {  todoListCreatePermission,  todoListUpdatePermission,} from '@internal/plugin-todo-list-common';import { getTodo } from './todos';import { todoListPermissionResourceRef, rules } from './rules';// ...permissionsRegistry.addPermissions([  todoListCreatePermission,  todoListUpdatePermission,]);permissionsRegistry.addResourceType({  resourceRef: todoListPermissionResourceRef,  permissions: [todoListCreatePermission, todoListUpdatePermission],  rules: Object.values(rules),  getResources: async resourceRefs => {    return Promise.all(resourceRefs.map(getTodo));  },});

정책 작성자를 위한 유틸리티 제공

이제 새 리소스 타입과 해당 규칙이 있으니, 정책 작성자가 그것들을 참조할 수 있도록 일부 유틸리티를 내보내야 해요.

새 plugins/todo-list-backend/src/conditionExports.ts 파일을 만들고 다음 코드를 추가하세요.

plugins/todo-list-backend/src/conditionExports.ts

import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common';import { createConditionExports } from '@backstage/plugin-permission-node';import { todoListPermissionResourceRef, rules } from './service/rules';const { conditions, createConditionalDecision } = createConditionExports({  resourceRef: todoListPermissionResourceRef,  rules,});export const todoListConditions = conditions;export const createTodoListConditionalDecision = createConditionalDecision;

plugins/todo-list-backend/src/index.ts를 편집해 todoListConditions와 createTodoListConditionalDecision이 todo-list-backend 패키지에서 내보내지도록 확인하세요.

plugins/todo-list-backend/src/index.ts

export * from './service/router';export * from './conditionExports';export { exampleTodoListPlugin } from './plugin';

인가된 업데이트 엔드포인트 테스트

권한 정책의 handle 함수로 돌아가 isOwner 조건으로 새 권한을 인가해 보겠어요. Getting Started 단계에서 만든 권한 정책 모듈의 CustomPolicy 클래스를 갱신하세요.

import {  PermissionPolicy,  PolicyQuery,  PolicyQueryUser,} from '@backstage/plugin-permission-node';import {  AuthorizeResult,  PolicyDecision,  isPermission,} from '@backstage/plugin-permission-common';import { UserInfoService } from '@backstage/backend-plugin-api';import { todoListCreatePermission } from '@internal/plugin-todo-list-common';import {  todoListCreatePermission,  todoListUpdatePermission,} from '@internal/plugin-todo-list-common';import {  todoListConditions,  createTodoListConditionalDecision,} from '@internal/plugin-todo-list-backend';export class CustomPolicy implements PermissionPolicy {  constructor(private readonly userInfo: UserInfoService) {}  async handle(    request: PolicyQuery,    _user?: PolicyQueryUser,    user?: PolicyQueryUser,  ): Promise<PolicyDecision> {    if (isPermission(request.permission, todoListCreatePermission)) {      return {        result: AuthorizeResult.ALLOW,      };    }    if (isPermission(request.permission, todoListUpdatePermission)) {      const userEntityRef = user        ? (await this.userInfo.getUserInfo(user.credentials)).userEntityRef        : '';      return createTodoListConditionalDecision(        request.permission,        todoListConditions.isOwner({          userId: userEntityRef,        }),      );    }    return {      result: AuthorizeResult.ALLOW,    };  }}

들어오는 업데이트 요청에 대해 이제 조건부 결정을 반환해요. 우리는 이렇게 말하는 거예요.

권한 프레임워크야, 나 혼자 결정할 수 없어. id가 todolist인 플러그인에 가서 이 조건들을 적용하라고 요청해 줘.

모든 것이 예상대로 작동하는지 확인하려면, 자신이 만들지 않은 항목을 편집하려고 할 때 UI에 오류가 표시되는 것을 볼 수 있어야 해요. 성공이에요!

더 알아보기 (Learn more)