23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
이 글에서는 크게 세가지 작업을 정리하고자 한다.
첫번째는 현재 개발 중인 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를 도입하여 데이터 동기화를 하는 등의 작업이 필요하다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.