
Object Storage와 RDB의 접근권한을 제어하는 서비스를 고도화 하기 위해 CNCF 프로젝트 중 하나인 OPA(Open Policy Agent)에 대해 조사(구글 번역 후 실습)해 보았습니다.
1. OPA Introduction
OPA (“oh-pa"로 발음)는 스택 전체에 정책 시행을 통합하는 오픈 소스 범용 정책 엔진입니다. OPA는 정책을 코드로 지정할 수있는 높은 수준의 선언적 언어와 소프트웨어에서 정책 의사 결정의 부담을 덜어주는 간단한 API를 제공합니다. OPA를 사용하여 마이크로 서비스, Kubernetes, CI / CD 파이프 라인, API 게이트웨이 등에서 정책을 시행 할 수 있습니다.
1.1 Overview
OPA는 정책 시행과 정책 의사 결정을 분리합니다. 소프트웨어가 정책 결정을 내려야 할 때 OPA를 쿼리하고 구조화 된 데이터 (ex. JSON)를 입력으로 제공합니다. OPA는 임의의 구조화 된 데이터를 입력으로 받아들입니다.
OPA는 쿼리 입력을 평가하고 정책 및 데이터에 대해 정책 결정을 생성합니다. OPA 및 Rego는 도메인에 구애받지 않으므로 정책에서 거의 모든 종류의 불변을 설명 할 수 있습니다.
예를 들면 :
- 어떤 사용자가 어떤 리소스에 액세스 할 수 있는지.
- 어떤 Subnet의 Egress Traffic이 허용 되는지.
- 워크로드가 어느 클러스터에 배포되어야 하는지.
- 어떤 Registries binaries로 부터 다운 받을수 있는지.
- 컨테이너가 어떤 OS Capabilities를 실행할수 있는지.
- 언제 시스템에 접근 할 수 있는지.
정책 결정은 단순한 예 / 아니오 또는 답변 허용 / 거부로 제한되지 않습니다. 쿼리 입력과 마찬가지로 정책은 임의의 구조화 된 데이터를 출력으로 생성 할 수 있습니다.
1.2 Rego
OPA 정책은 Rego라는 고급 선언 언어로 표현됩니다. Rego ( “ray-go"로 발음)는 복잡한 계층적 데이터 구조에 대한 정책을 표현하기 위해 특별히 제작되었습니다. Rego에 대한 자세한 정보는 Policy Language 문서를 참조하십시오.
1.3 Example
시스템에는 세 가지 종류의 구성 요소가 있습니다.
- 서버는 0 개 이상의 프로토콜 (예 : http, ssh 등)을 노출합니다.
- 네트워크는 서버를 연결하며 Public 또는 Private이 될 수 있습니다. Public 네트워크는 인터넷에 연결됩니다.
- 포트는 서버를 네트워크에 연결합니다.
모든 서버, 네트워크 및 포트는 스크립트로 프로비저닝됩니다. 스크립트는 시스템의 JSON 표현을 입력으로받습니다.
1.3.1 시스템의 JSON 표현
{
"servers": [
{"id": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]},
{"id": "db", "protocols": ["mysql"], "ports": ["p3"]},
{"id": "cache", "protocols": ["memcache"], "ports": ["p3"]},
{"id": "ci", "protocols": ["http"], "ports": ["p1", "p2"]},
{"id": "busybox", "protocols": ["telnet"], "ports": ["p1"]}
],
"networks": [
{"id": "net1", "public": false},
{"id": "net2", "public": false},
{"id": "net3", "public": true},
{"id": "net4", "public": true}
],
"ports": [
{"id": "p1", "network": "net1"},
{"id": "p2", "network": "net3"},
{"id": "p3", "network": "net2"}
]
}
1.3.2 정책 예제
새로운 보안 정책에 대해 다음과 같습니다.
- 인터넷에서 연결할 수있는 서버는 안전하지 않은 ‘http’프로토콜을 노출해서는 안됩니다.
- 서버는 ‘텔넷’프로토콜을 노출 할 수 없습니다.
정책은 서버, 네트워크 및 포트가 프로비저닝되고 규정 준수 팀이 정책을 위반하는 서버를 찾기 위해 시스템을 주기적으로 감사하려고 할 때 적용되어야합니다.
package example
allow = true { # allow is true if...
count(violation) == 0 # there are zero violations.
}
violation[server.id] { # a server is in the violation set if...
some server
public_server[server] # it exists in the 'public_server' set and...
server.protocols[_] == "http" # it contains the insecure "http" protocol.
}
violation[server.id] { # a server is in the violation set if...
server := input.servers[_] # it exists in the input.servers collection and...
server.protocols[_] == "telnet" # it contains the "telnet" protocol.
}
public_server[server] { # a server exists in the public_server set if...
some i, j
server := input.servers[_] # it exists in the input.servers collection and...
server.ports[_] == input.ports[i].id # it references a port in the input.ports collection and...
input.ports[i].network == input.networks[j].id # the port references a network in the input.networks collection and...
input.networks[j].public # the network is public.
}
- public_server로 서버 정보 collection을 정의
- violation으로 위반 사항을 정의
- violation count가 0인경우 allow
1.4 OPA 실행
1.4.1 Download OPA
GitHub 릴리스에서 플랫폼 용 OPA 바이너리를 다운로드 받아 시작할 수 있습니다.
On macOS (64-bit):
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_darwin_amd64
On Linux (64-bit):
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
OPA 실행파일의 permission을 설정합니다.
chmod 755 ./opa
Docker를 통해서도 실행 가능 합니다. 최신 stable 이미지 태그 : openpolicyagent/opa:latest
docker run -it --rm -p 8181:8181 openpolicyagent/opa run --server --addr :8181
1.4.2 Run
./opa eval 1=1
./opa eval 1=1
{
"result": [
{
"expressions": [
{
"value": true,
"text": "1=1",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
./opa eval "1*2+3"
./opa eval "1*2+3"
{
"result": [
{
"expressions": [
{
"value": 5,
"text": "1*2+3",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
./opa eval -i input.json -d example.rego "data.example.violation[x]"
./opa eval -i input.json -d example.rego "data.example.violation[x]"
{
"result": [
{
"expressions": [
{
"value": "ci",
"text": "data.example.violation[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": "ci"
}
},
{
"expressions": [
{
"value": "busybox",
"text": "data.example.violation[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": "busybox"
}
}
]
}
./opa eval --fail-defined -i input.json -d example.rego "data.example.violation[x]"
./opa eval --fail-defined -i input.json -d example.rego "data.example.violation[x]"
{
"result": [
{
"expressions": [
{
"value": "ci",
"text": "data.example.violation[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": "ci"
}
},
{
"expressions": [
{
"value": "busybox",
"text": "data.example.violation[x]",
"location": {
"row": 1,
"col": 1
}
}
],
"bindings": {
"x": "busybox"
}
}
]
}
1.4.3 Run Server
./opa run --server example.rego
{"addrs":[":8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2021-01-28T13:17:10+09:00"}
- input.json을 Post 입력 형식으로 생성
cat <<EOF > v1-data-input.json
{
"input": $(cat input.json)
}
EOF
- input.json의 violation 확인
curl localhost:8181/v1/data/example/violation -d @v1-data-input.json -H 'Content-Type: application/json'
{"result":["ci","busybox"]}%
- input.json의 allow 확인
curl localhost:8181/v1/data/example/allow -d @v1-data-input.json -H 'Content-Type: application/json'
{"result":false}%
- 요청들의 서버 로그
{"client_addr":"127.0.0.1:53817","level":"info","msg":"Received request.","req_id":1,"req_method":"POST","req_path":"/v1/data/example/violation","time":"2021-01-28T13:17:30+09:00"}
{"client_addr":"127.0.0.1:53817","level":"info","msg":"Sent response.","req_id":1,"req_method":"POST","req_path":"/v1/data/example/violation","resp_bytes":13,"resp_duration":4.727648,"resp_status":200,"time":"2021-01-28T13:17:30+09:00"}
{"client_addr":"127.0.0.1:53836","level":"info","msg":"Received request.","req_id":2,"req_method":"POST","req_path":"/v1/data/example/violation","time":"2021-01-28T13:17:49+09:00"}
{"client_addr":"127.0.0.1:53836","level":"info","msg":"Sent response.","req_id":2,"req_method":"POST","req_path":"/v1/data/example/violation","resp_bytes":27,"resp_duration":0.952211,"resp_status":200,"time":"2021-01-28T13:17:49+09:00"}
{"client_addr":"127.0.0.1:53854","level":"info","msg":"Received request.","req_id":3,"req_method":"POST","req_path":"/v1/data/example/allow","time":"2021-01-28T13:18:03+09:00"}
{"client_addr":"127.0.0.1:53854","level":"info","msg":"Sent response.","req_id":3,"req_method":"POST","req_path":"/v1/data/example/allow","resp_bytes":16,"resp_duration":1.156257,"resp_status":200,"time":"2021-01-28T13:18:03+09:00"}