데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      OPA(Open Policy Agent)를 이용한 JIRA의 권한 구현하기

      sunny79 23.04.13
      1,436 18 0

      OPA(Open Policy Agent)를 이용한 JIRA의 권한 구현하기

      • 이 글에서는 크게 세가지 작업을 정리하고자 한다.

      • 첫번째는 현재 개발 중인 ITSM 시스템에서 권한 부분을 OPA를 이용해 구현하고자 한다.

        • 정책을 rego로 작성해본다.

      • 두번째는 작성된 정책을, data API를 이용해 권한 체크를 해본다.

      • 세번째는 애플리케이션과 OPA 서버를 하나의 파드로 묶어 사이드카 형태로 쿠버네티스 개발 환경에 배포해본다.


      본문

      • ITSM 시스템의 백엔드는 자바 SRPING, JPA 사용하여 개발 중이며, 현재 사용 중인 JIRA를 대체할 예정이다.

      • JIRA의 데이터를 이전할 계획이라, JIRA의 데이터 구조를 그대로 사용하고자한다.

      • JIRA의 권한 관련 데이터 및 권한 정책을 확인해 보자.

      • JIRA의 프로젝트 관리 권한을 가진 사용자라면 아래와 같은 페이지에서 프로젝트의 롤과 퍼미션을 수정할 수 있다.

      • Users and Roles 에서 ADMINISTRATORS/DEVELOPERS/USERS 롤에 그룹및 사용자를 할당할 수 있다.


      • Project Permissions 에서 각 퍼미션에 사용자, 그룹 및 위에서 할당한 롤을 추가할 수 있다.


      • 이렇게 화면에서 설정한 값은 JSON으로 아래와 같이 표현된다.(JIRA API를 통해 확인할 수 있다.)

        • 파일 크기가 꽤 되므로 일부 내용만 캡쳐한다. permissionKey(퍼미션 내용)과 grants(권한부여) 을 포함한 동일한 형태의 퍼미션을 배열로 정의하고있다.

        • 해당 JSON을 OPA에서 권한 체크시, 데이터로 사용하자.



      • 이러한 구조를 염두해 두고 권한처리를 생각해보면, 사용자 및 사용자의 그룹이 속한 롤을 확인하고,

      • 사용자 아이디, 그룹명, 롤명으로 permissions의 grants를 뒤져서 해당 사용자가 해당 퍼미션에 권한이 있는지 여부를 확인할 수 있다.

      • 복잡해 보이지만 그외로 간단하다. Rego를 작성해보자

      package app.rbac
      
      import future.keywords.if
      
      # By default, deny requests.
      default allow := false
      
      allow {
         # Find grants for the permissionKey.
         user_is_granted[_] == input.permissionKey
      }
      
      user_is_granted := grant if {
         #내가 가진 프로젝트 롤
         myRoleWithUser := {ro.id | ro = data[input.project.key].roles[_]; ro.users[_].name = input.user}
      
         #내가 포함된 그룹이 가진 프로젝트 롤
         myRoleWithGroup := {ro.id | ro = data[input.project.key].roles[_]; ro.groups[_].name = input.groups[_]}
      
         #권한 체크를 하고자 하는 프로젝트의 퍼미션조회
         perm := {pe | pe = data[input.project.key].permissions[_]}
      
         #나의 롤에게 부여된 퍼미션
         grantOfUserRole := {gr | gr = perm[ur].permissionKey; to_number(perm[ur].grants[_].values[_].value) == myRoleWithUser[_]}
      
         #나의 그룹의 롤에게 부여된 퍼미션
         grantOfGroupRole := {gr | gr = perm[gr].permissionKey; to_number(perm[gr].grants[_].values[_].value) == myRoleWithGroup[_]}
      
         #나에게 부여된 퍼미션
         grantOfUser := {gr | gr = perm[u].permissionKey; perm[u].grants[_].values[_].value == input.user}
      
         #나의 그룹에게 부여된 퍼미션
         grantOfGroup := {gr | gr = perm[g].permissionKey; perm[g].grants[_].values[_].value == input.groups[_]}
      
         #위의 퍼미션의 합집합
         grant := grantOfUserRole | grantOfGroupRole | grantOfUser | grantOfGroup
      }
      • 작성한 정책(rego)과 데이터을 이용하여 권한 체크를 해보자. OPA 플레이그라운드에서 테스트 해볼 수 있다.

        • https://play.openpolicyagent.org/p/7dwdCy843A

      • 백단 서버에서 OPA data API를 이용하여  권한 체크를 하는 로직을 개발해보자.

      • Spring security의 PreAuthorize 태그를 이용한다.

      • Spring controller에 권한 체크를 위해 PreAuthorize 태그를 추가한다.

      • PreAuhorize에서 opaclient.allow를 호출한다.

      @PreAuthorize("@opaClient.allow('CREATE_ATTACHMENTS',T(java.util.Map).of('key', #key))")
      @PostMapping(value = "{version}/issue/{key}/attachments")
      @ResponseBodypublic ResponseEntity uploadFile(@PathVariable String version,@PathVariable String key,
                             @RequestParam List<MultipartFile> file) throws IOException {
          List<TicketFile> ticketFiles = ticketService.uploadFiles(key, file);
          return new ResponseEntity(ticketFiles, HttpStatus.OK);
      
      }
      @PreAuthorize("@opaClient.allow('TRANSITION_ISSUES',T(java.util.Map).of('key', #key))")
      @PutMapping(value="/issue/{key}/{issueEvent}")
      @ResponseBodypublic ResponseEntity updateState(@PathVariable String key,@PathVariable String issueEvent){
          IssueEvent event = IssueEvent.valueOf(issueEvent);
      
          Ticket ticket = ticketService.updateIssueState(key, event);
          return new ResponseEntity(ticket, HttpStatus.OK);
      }
      • opaClient.allow는 아래와 같이 구현하였다.

      • input으로 사용자 아이디와 그룹, 권한을 체크하고자 하는 프로젝트와 퍼미션를 입력한다.

      //URI는 OPA 서버의 endpoint로 http://localhost:8181 설정한다.
      @Value("${opa.url}")
      String URI;
      
      public boolean allow(String action, Map<String, Object> resourceAttributes) {
              Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
              if (authentication == null || !authentication.isAuthenticated() || action == null 
                                   || resourceAttributes == null || resourceAttributes.isEmpty()) {
                  return false;
              }
      
              String name = authentication.getName();
              List<String> authorities = authentication.getAuthorities()
                      .stream()
                      .map(GrantedAuthority::getAuthority)
                      .collect(Collectors.toUnmodifiableList());
      
              Object project = null;
      
              if (resourceAttributes.containsKey("key")){
                  String issueKey = (String) resourceAttributes.get("key");
                  String projectKey = issueKey.substring(0, issueKey.indexOf("-"));
                  project = Map.of("key", projectKey);
              }
      
              /**
               * {
               *     "groups": [
               *         "ITSM-users"
               *     ],
               *     "permissionKey": "ADD_COMMENTS",
               *     "projectKey":"TEMP",
               *     "user": "1004196"
               * }
               */
              Map<String, Object> input = Map.of(
                      "user", name,
                      "permissionKey", action,
                      "project", project,
                      "groups", authorities
              );
      
              ObjectNode requestNode = objectMapper.createObjectNode();
              requestNode.set("input", objectMapper.valueToTree(input));
              log.info("Authorization request:\n" + requestNode.toPrettyString());
      
              JsonNode responseNode = Objects.requireNonNull(restTemplate.postForObject(URI+"/v1/data/app/rbac/allow",
                                                                                        requestNode, JsonNode.class));
              log.info("Authorization response:\n" + responseNode.toPrettyString());
      
              return responseNode.has("result") && responseNode.get("result").asBoolean();
          }
      • 개발한 백단서버와 OPA서버을 쿠버네티스 환경에 개발 서버로 배포해보자.

      • 아래 k8s deployment yaml를 이용하여, 백단서버(pitsm-backend)와 OPA서버를 한 pod 내에 container로 배포한다.

      • OPA 서버를 사이드카 형태로 실행하면 아래와 같은 장점이 있다.

        • localhost:8181을 통해 권한을 체크하므로, 네트웍 지연을 없앨 수 있다.

        • 애플리케이션과 함께 배포되며, 애플리케이션과 함게 스케일링되어 운영 및 관리가 용이하다.

        • 단점도 있다. data를 수정하는 경우, 사이드카 형태의 각 OPA 서버에게 data 수정 API를 요청해야 한다.

        • 메시지 브로커를 사용하여 각 애플리케이션에서 이벤트를 받아 data를 수정하도록 한다.

        • 또는 OPAL을 이용하여 OPA의 정책과 데이터를 동기화할 수 있다.

      • OPA 서버를 시작할때 /policies 경로에 정책을 사용하도록 설정하였다.

      • opa-policy configmap을 rego파일을 이용해 생성하고, /policies 경로에 마운트한다.

          spec:
            volumes:
              - name: opademo-policy
                configMap:
                  name: opademo-policy
                  defaultMode: 420
            containers:
              - name: pitsm-backend
                image: dev.pcr.kr/devops/pitsm-backend:20230328142251          env:
                  - name: SPRING_PROFILES_ACTIVE
                    value: kube
                resources: {}
                terminationMessagePath: /dev/termination-log
                terminationMessagePolicy: File
                imagePullPolicy: IfNotPresent
                securityContext:
                  privileged: false
              - name: opa
                image: openpolicyagent/opa:latest
                args:
                  - run
                  - '--ignore=.*'
                  - '--server'
                  - /policies
                ports:
                  - name: http
                    containerPort: 8181
                    protocol: TCP
                resources: {}
                volumeMounts:
                  - name: opademo-policy
                    readOnly: true
                    mountPath: /policies


      마무리

      • 권한 기능을 자바로 구현할까도 생각했지만, 구현의 단순함이나 성능에서 OPA로 구현한 것에 만족한다.

      • 자바로 구현했다면 데이터의 저장을 위해, 퍼미션 및 롤 데이터를 위한 스키마 설계 부터 머리가 아플 듯하다.

      • 여러 개의 테이블로 구성하고 관계를 설정할 생각을 하면 머리가 아프다.

      • 현재는 JSON 형태의 데이터를 통으로 DB 컬럼에 저장 중이다.

      • DB에서 데이터를 조회해서 권한 체크를 할 것이 아니기 때문에, 굳이 테이블을 나눠서 인덱스를 생각할 필요가 없다.

      • OPA는 데이터를 메모리에 올려놓고 실행되기 때문에 빠르게 동작할꺼라 기대한다.

      • 또한 사이드카로 구성하여, 어플리케이션에서 권한 요청시 네트웍 지연을 없앤 것도 맘에 든다.

      • 다만 데이터 동기화를 위한, 추가 작업이 필요하다.

        • 메시지 브로커를 이용해 어플리케이션에서 업데이트 작업을 하거나,

        • OPAL를 도입하여 데이터 동기화를 하는 등의 작업이 필요하다.


      Reference

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      sunny79 님의 최신 블로그

      더보기
      동영상 기고하기