Skip to content

Commit 2cf56d7

Browse files
committed
Initial commit
0 parents  commit 2cf56d7

25 files changed

+541
-0
lines changed

README.md

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# RBAC on Nginx with Open Policy Agent
2+
3+
This repository demonstrates how to implement role based access control (RBAC) on Nginx with Open Policy Agent.
4+
5+
## How to use
6+
7+
First, start the container of Nginx and Open Policy Agent.
8+
9+
```
10+
$ docker-compose up -d
11+
```
12+
13+
When you send a request to `/compute/` using Alice's client certificate, nginx will grant access based on role definition.
14+
15+
```
16+
$ curl -k --cert ./nginx/tls/alice.pem --key ./nginx/tls/alice-key.pem https://127.0.0.1:8080/compute/
17+
```
18+
19+
For requests to `/storage/` as well, nginx allows too.
20+
21+
```
22+
$ curl -k --cert ./nginx/tls/alice.pem --key ./nginx/tls/alice-key.pem https://127.0.0.1:8080/storage/
23+
```
24+
25+
Access is allowed even if you send a request to `/compute/` using Bob's client certificate.
26+
27+
```
28+
$ curl -k --cert ./nginx/tls/bob.pem --key ./nginx/tls/bob-key.pem https://127.0.0.1:8080/compute/
29+
```
30+
31+
However, Bob can not access to `/storage/` based on role definition.
32+
33+
```
34+
$ curl -k --cert ./nginx/tls/bob.pem --key ./nginx/tls/bob-key.pem https://127.0.0.1:8080/storage/
35+
```
36+
37+
## Generating a new client certificate
38+
39+
This repository uses a client certificate for user authentication. We use [CFSSL](https://github.com/cloudflare/cfssl) to issue client certificates. How to issue a new client certificate as follows.
40+
41+
Generate a new CSR definition of CFSSL.
42+
43+
```
44+
$ cd nginx/tls
45+
$ cfssl print-defaults csr > client-csr.json
46+
```
47+
48+
Rewrite the CSR definition as you need.
49+
50+
```
51+
$ vim client-csr.json
52+
```
53+
54+
Generate client certificate files by using CSR definition.
55+
56+
```
57+
$ cfssl gencert -ca=ca.pem -ca-key=ca-key.pem client-csr.json | cfssljson -bare client
58+
```
59+
60+
You can use the new client certificate.
61+
62+
```
63+
$ curl -k --cert ./nginx/tls/client.pem --key ./nginx/tls/client-key.pem https://127.0.0.1:8080/compute
64+
```
65+
66+
## Defining roles and role bindings
67+
68+
Authorization by RBAC is implemented by the combination of Nginx and Open Policy Agent.
69+
70+
The Role definition is defined in the JSON file as follows. The role has a combination of a path and an list of HTTP methods allowing access, and the Open Policy Agent performs authorization based on the role.
71+
72+
```
73+
{
74+
"roles": {
75+
"compute.admin": {
76+
"/compute/": ["GET", "POST", "PUT", "DELETE"]
77+
},
78+
"compute.viewer": {
79+
"/compute/": ["GET"]
80+
},
81+
"storage.admin": {
82+
"/storage/": ["GET", "POST", "PUT", "DELETE"]
83+
},
84+
"storage.viewer": {
85+
"/storage/": ["GET"]
86+
}
87+
},
88+
89+
"role_bindings": {
90+
"alice": ["compute.admin", "storage.admin"],
91+
"bob": ["compute.viewer"]
92+
}
93+
}
94+
```
95+
96+
You can add a new role or role binding to the role definition.
97+
98+
```
99+
$ vim opa/rbac.json
100+
```

docker-compose.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
version: '3'
2+
services:
3+
opa:
4+
image: openpolicyagent/opa:0.8.1
5+
ports:
6+
- 8181:8181
7+
volumes:
8+
- ./opa:/etc/opa
9+
command:
10+
- run
11+
- --server
12+
- --log-level=debug
13+
- /etc/opa/rbac.json
14+
- /etc/opa/rbac.rego
15+
16+
web:
17+
image: nginx:1.13.12
18+
volumes:
19+
- ./web/html:/usr/share/nginx/html:ro
20+
21+
nginx:
22+
image: nginx:1.13.12
23+
ports:
24+
- 8080:8080
25+
volumes:
26+
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
27+
- ./nginx/conf.d:/etc/nginx/conf.d
28+
- ./nginx/tls:/etc/nginx/tls
29+
depends_on:
30+
- opa
31+
- web
32+

nginx/conf.d/rbac.conf

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
js_include /etc/nginx/conf.d/rbac.js;
2+
3+
server {
4+
listen 8080 ssl http2;
5+
6+
ssl_certificate /etc/nginx/tls/server.pem;
7+
ssl_certificate_key /etc/nginx/tls/server-key.pem;
8+
ssl_client_certificate /etc/nginx/tls/ca.pem;
9+
ssl_verify_client on;
10+
11+
location / {
12+
auth_request /_authz;
13+
proxy_pass http://web;
14+
}
15+
16+
location = /_authz {
17+
internal;
18+
js_content authz;
19+
}
20+
21+
location = /_opa {
22+
internal;
23+
proxy_pass http://opa:8181/v1/data/httpapi/rbac;
24+
}
25+
}

nginx/conf.d/rbac.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
function authz(req, res) {
2+
var dn = req.variables.ssl_client_s_dn;
3+
if (dn == "-") {
4+
res.return(403);
5+
return;
6+
}
7+
8+
req.error("Client certificate: " + dn);
9+
10+
var match = RegExp('CN=([^,]+),?').exec(dn);
11+
if (match == null) {
12+
res.return(403);
13+
return;
14+
}
15+
16+
var opa_data = {
17+
"input": {
18+
"user": match[1],
19+
"path": req.variables.request_uri,
20+
"method": req.variables.request_method
21+
}
22+
};
23+
24+
var opts = {
25+
method: "POST",
26+
body: JSON.stringify(opa_data)
27+
};
28+
29+
req.subrequest("/_opa", opts, function(opa) {
30+
req.error("OPA response: " + opa.body);
31+
32+
var body = JSON.parse(opa.body);
33+
if (!body.result) {
34+
res.return(403);
35+
return;
36+
}
37+
38+
if (!body.result.allow) {
39+
res.return(403);
40+
return;
41+
}
42+
43+
res.return(200);
44+
});
45+
}

nginx/nginx.conf

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
load_module modules/ngx_http_js_module.so;
2+
3+
user nginx;
4+
worker_processes 1;
5+
6+
error_log /var/log/nginx/error.log warn;
7+
pid /var/run/nginx.pid;
8+
9+
10+
events {
11+
worker_connections 1024;
12+
}
13+
14+
http {
15+
include /etc/nginx/mime.types;
16+
default_type application/octet-stream;
17+
18+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
19+
'$status $body_bytes_sent "$http_referer" '
20+
'"$http_user_agent" "$http_x_forwarded_for"';
21+
22+
access_log /var/log/nginx/access.log main;
23+
24+
sendfile on;
25+
#tcp_nopush on;
26+
27+
keepalive_timeout 65;
28+
29+
#gzip on;
30+
31+
include /etc/nginx/conf.d/*.conf;
32+
}

nginx/tls/alice-csr.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"CN": "alice",
3+
"key": {
4+
"algo": "ecdsa",
5+
"size": 256
6+
},
7+
"names": [
8+
{
9+
"C": "JP",
10+
"L": "Tokyo",
11+
"O": "Demo"
12+
}
13+
]
14+
}
15+

nginx/tls/alice-key.pem

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIBss7j6DnjwG9XiUvn15NHFeGIXnJy+AcNDVLcCY97wJoAoGCCqGSM49
3+
AwEHoUQDQgAEHtaOsdAdnjatm9ME8IShLaiMYamGbLmZor9tI2V4HfVi5WNy+JRf
4+
QImJKvW8k26s9zPTLmRtSTOMTr/nZxMjuw==
5+
-----END EC PRIVATE KEY-----

nginx/tls/alice.csr

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIH2MIGeAgEAMDwxCzAJBgNVBAYTAkpQMQ4wDAYDVQQHEwVUb2t5bzENMAsGA1UE
3+
ChMERGVtbzEOMAwGA1UEAxMFYWxpY2UwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
4+
AAQe1o6x0B2eNq2b0wTwhKEtqIxhqYZsuZmiv20jZXgd9WLlY3L4lF9AiYkq9byT
5+
bqz3M9MuZG1JM4xOv+dnEyO7oAAwCgYIKoZIzj0EAwIDRwAwRAIgdXAq9v7elbrJ
6+
E8Op80XXCHcamPeNHhqB9aJaVy7bnOcCIGduvOHxbRv5gVxFcMJN+rK506MaraEm
7+
uamk98bHFAkK
8+
-----END CERTIFICATE REQUEST-----

nginx/tls/alice.pem

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICvDCCAaSgAwIBAgIUMOUyOfhS2nk/+yBytalZzszeZeswDQYJKoZIhvcNAQEL
3+
BQAwPjELMAkGA1UEBhMCSlAxDjAMBgNVBAcTBVRva3lvMQ0wCwYDVQQKEwREZW1v
4+
MRAwDgYDVQQDEwdEZW1vIENBMB4XDTE4MDUyNjE0MDYwMFoXDTE5MDUyNjE0MDYw
5+
MFowPDELMAkGA1UEBhMCSlAxDjAMBgNVBAcTBVRva3lvMQ0wCwYDVQQKEwREZW1v
6+
MQ4wDAYDVQQDEwVhbGljZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABB7WjrHQ
7+
HZ42rZvTBPCEoS2ojGGphmy5maK/bSNleB31YuVjcviUX0CJiSr1vJNurPcz0y5k
8+
bUkzjE6/52cTI7ujfzB9MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEF
9+
BQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUhXoQb7ghdMPy
10+
lPm6PSzKM/jRtwUwHwYDVR0jBBgwFoAUKYEGLnvF3aHzFBLBHtEMe8tt84kwDQYJ
11+
KoZIhvcNAQELBQADggEBAFdWdv55jUtXs1CazLJLnFA2s7Az4U7WxyA/UkBPNBSa
12+
tleseDVhJnvjfv5DN5p24fW0CwTfJY7hnaWIjaM0rgFU9Xq3MO69zmcp+9rl+Ih7
13+
KTd+lI8R2cq8T79cest36m1M/mYLeEOZ2AngC6OvN/QUf5jEfDSk5S1GWA4Qv/cZ
14+
7rqHriJVmZkGe5XviGAxz1/kYhQyfzCPRo7jtonbJigqWV4cWDdkVX0WmCvn4bHr
15+
99HRhHNbVdCVwLrV43uh1JP1Wg8+sbhZ6ioaDZ0Fwn/S1Z2isFJz9vUf/b3UtGCF
16+
7WGFWCnuG6vEiOFNl9Rcp65VUpCS8gclFfZwb9sAZTw=
17+
-----END CERTIFICATE-----

nginx/tls/bob-csr.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"CN": "bob",
3+
"key": {
4+
"algo": "ecdsa",
5+
"size": 256
6+
},
7+
"names": [
8+
{
9+
"C": "JP",
10+
"L": "Tokyo",
11+
"O": "Demo"
12+
}
13+
]
14+
}
15+

nginx/tls/bob-key.pem

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEINcvQViIGbGENC1RuPcd2wJTpk0spCNXP9sKoC3qC+anoAoGCCqGSM49
3+
AwEHoUQDQgAE5NhT5VMwJe1/Gy1dvChtv8Phlqd9ZeD1yIG/wzxAAzLT1DGeihQ8
4+
SWa+82X4Jy2EPALbtS65mq5+ufqwFum14Q==
5+
-----END EC PRIVATE KEY-----

nginx/tls/bob.csr

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIH2MIGcAgEAMDoxCzAJBgNVBAYTAkpQMQ4wDAYDVQQHEwVUb2t5bzENMAsGA1UE
3+
ChMERGVtbzEMMAoGA1UEAxMDYm9iMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
4+
5NhT5VMwJe1/Gy1dvChtv8Phlqd9ZeD1yIG/wzxAAzLT1DGeihQ8SWa+82X4Jy2E
5+
PALbtS65mq5+ufqwFum14aAAMAoGCCqGSM49BAMCA0kAMEYCIQDBKYKeOkhgAS1g
6+
cwJLBv0SgvYJ/AtqwxXwq5CoA6zMKQIhAOrzfwZUyoyCs/VgvK6qFz9ULqSF9J1l
7+
b7TIo78KGqTu
8+
-----END CERTIFICATE REQUEST-----

nginx/tls/bob.pem

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICujCCAaKgAwIBAgIUSfvCBghMsQVOBOXv+jR6O0ZthpYwDQYJKoZIhvcNAQEL
3+
BQAwPjELMAkGA1UEBhMCSlAxDjAMBgNVBAcTBVRva3lvMQ0wCwYDVQQKEwREZW1v
4+
MRAwDgYDVQQDEwdEZW1vIENBMB4XDTE4MDUyNjE0MDYwMFoXDTE5MDUyNjE0MDYw
5+
MFowOjELMAkGA1UEBhMCSlAxDjAMBgNVBAcTBVRva3lvMQ0wCwYDVQQKEwREZW1v
6+
MQwwCgYDVQQDEwNib2IwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATk2FPlUzAl
7+
7X8bLV28KG2/w+GWp31l4PXIgb/DPEADMtPUMZ6KFDxJZr7zZfgnLYQ8Atu1Lrma
8+
rn65+rAW6bXho38wfTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUH
9+
AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFK9xy46/dNn+aLE2
10+
L3yX+45pbZvQMB8GA1UdIwQYMBaAFCmBBi57xd2h8xQSwR7RDHvLbfOJMA0GCSqG
11+
SIb3DQEBCwUAA4IBAQC9Ic34W/TkTKZ8vTecFb+deXoBH/Bmk2vlzDQNJnVfazx+
12+
Rg8aO73RE/IRvUMlT8mKV9gS0MR3rhL/kjJ5LXyATkJYIFfuw7BmPaGNllbM8iZl
13+
RXhfR+ql3PaKIFA/vlDmxmBP+96AuaQfZcRiqbWaNmTkJFjZhVrSCPWT0SBmc7Jk
14+
XafOlPVnNMOmDHI19OGAjdoANe2HK4cVscaRak1JsdGEOSCJsIHQyBIj2HZKyc/P
15+
jLIXVRm7lt7dNVMM2yY+uY4wbbFU3UPG/reAErrHvcee0RtMWGMJr148MJl1KQ+g
16+
fwvB1FygFjE9puIVRz9POu+BV628x8FuVjze9M+U
17+
-----END CERTIFICATE-----

nginx/tls/ca-csr.json

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"CN": "Demo CA",
3+
"key": {
4+
"algo": "rsa",
5+
"size": 2048
6+
},
7+
"names": [
8+
{
9+
"C": "JP",
10+
"L": "Tokyo",
11+
"O": "Demo"
12+
}
13+
]
14+
}

nginx/tls/ca-key.pem

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpAIBAAKCAQEAvaLWQnYBOClxejYQzzmfSnE9R1PlqaR7C9g0qI4hasoxAs7j
3+
b1XdaUUZMfdW8d/1GnU7ufuWF9TaA30zVknp0NxIi8oqqyeda4u9zMQI8937LrXB
4+
/Mo9yQimsgdKIDoZMfEVdYO51A+ViiCAqmDEeihj61eBWNpgYwXot+Ym4wLI66iD
5+
FJcMpZGIqpHg1y+v7+TLpObh3P1JNLOtYIqXfuYupnD29TtEQetqhbaLVrOxOq7g
6+
tECU6JgKyiQlWtfpY4CMqoSH0BSgqNhqxwXYQpry+vjWmBDos0Y42C1ZT5lKazHJ
7+
/1i+bRAlVLPUfdVyVbNAFEkMO6SiGxe06sjk2QIDAQABAoIBACcYVgG3oEK60Ik4
8+
cji2kW9gbxiwZC2YGkHz3c9OFfeVHYuNqXe/hPj55NrXOhZ9bGN6/cg25Nee6x9D
9+
BX1pmYmUkGQ5VpiYfyy3z3ZSh+H2xpz3nbmG3DwAy5TyScbhE2anZBwo/vuIBvVG
10+
BCCb+IWSpB7VmHX/91US18pp6WRtSI9CRMtu9qBUpRKUKTO6Zo1mwn6lVy+uRsjR
11+
y4YQH1wTN960SWZ2FNv2R+N77giiSHuGokmDju1ZwxHNtY6Ml4E1b1iW30iqysRE
12+
Wp2glHp0NlVOHGV8GHCctyeuuh82bUP6WVTdj2/l+kW3pShk/ZQN1tw8hG+WY+An
13+
i8glYD0CgYEA0F2PccNoe1R0vYQqYK/s/5sTB1s009V0fF4UgG1m+WNABpUqK/3L
14+
bL2bVVhRS8hQu53P5A/qNITyMtk73tksBH2DJWj6rwRgr17eak1sG0BdhHKLSMw+
15+
R0LMFbU5lFmKmY6/8tfBQKmnJWzQTQdW4lnsShmnwE9tICuSMpJIz8MCgYEA6P0n
16+
EaA8TbAD9vPUNOozPcJn7YdpJd1xcq8pfGsaRLqR5xQ13ud0elneIpi8lieXGc5g
17+
85UMuBlcSAFDYyGfpyvQCE87v2B6OFwp9dOsYXQjr6VQg1Nrvzr8PEDfYrNQVcD1
18+
ifB+kc0VoJYrb/eZmd+OcSfLNvb7eRiEFty7azMCgYEAlCOQkm89X0GiZgMLJgat
19+
1uRn2PkNS/YchTdWGCCv72qS4Js4imI8OKltQHY0Bk76pwkB/sEZ4BENKP2tRTjd
20+
xKt/jB9g6wGPw98M/kLhM1bFph7RzAX52SwycNSRhVlL4vTMn1iputFjVoZQahNn
21+
wDHyfpRS4bUWfqK7pFzAi4UCgYBYaT/7E0fu3v0SKAJ9teWN6QiQ/RJsePSE5W0j
22+
tmy4aefVvTiYBlKP3yxJCpZ9kDZpZ4QoyoWSEqWO+VO9+VNhF2IQ1ShB/fVDD84o
23+
Z5OBQ5YLH/tGalB3t4Vhw+hAxvSUJe3G00jkQOOVFYcULOvPlSKzU7tsdxqEIEZ3
24+
enlwOwKBgQDQCbI+jvbjdApBDWFOC2JGOkizIBe1QAfLWkD9Rdvxe87R800OxnT7
25+
9qTUCizt31z60fJ0qWaoosRx3XAAzgvmYC/GH8qxHJa3AsJi8AMC146nbnxfPPeA
26+
Nm4Ax0yYKkb0r1SK05VHAxSKljGckahJjFU/9LIad6MzX371Yd7TEw==
27+
-----END RSA PRIVATE KEY-----

nginx/tls/ca.csr

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE REQUEST-----
2+
MIICgzCCAWsCAQAwPjELMAkGA1UEBhMCSlAxDjAMBgNVBAcTBVRva3lvMQ0wCwYD
3+
VQQKEwREZW1vMRAwDgYDVQQDEwdEZW1vIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
4+
AQ8AMIIBCgKCAQEAvaLWQnYBOClxejYQzzmfSnE9R1PlqaR7C9g0qI4hasoxAs7j
5+
b1XdaUUZMfdW8d/1GnU7ufuWF9TaA30zVknp0NxIi8oqqyeda4u9zMQI8937LrXB
6+
/Mo9yQimsgdKIDoZMfEVdYO51A+ViiCAqmDEeihj61eBWNpgYwXot+Ym4wLI66iD
7+
FJcMpZGIqpHg1y+v7+TLpObh3P1JNLOtYIqXfuYupnD29TtEQetqhbaLVrOxOq7g
8+
tECU6JgKyiQlWtfpY4CMqoSH0BSgqNhqxwXYQpry+vjWmBDos0Y42C1ZT5lKazHJ
9+
/1i+bRAlVLPUfdVyVbNAFEkMO6SiGxe06sjk2QIDAQABoAAwDQYJKoZIhvcNAQEL
10+
BQADggEBAJzAm4HnnFC0yiAu3bPUAA8CxxWkpn7KCIn3F3xrkR8UOMKQn1uWWaaD
11+
uzCfW5HZh8nOHyOjpLLUwPYxiT19dOJ3muqs+ReKcQ7KH1db88h2c8fbvH5rPlBT
12+
7slWV3xwjJN7b6WdFlpxzB2eW8nAsgHRXJIpJzNZpPLDCLXJkCdG5K8hbI0QwwMC
13+
KeDShHNB1nXZlZJPx7WXFz7IkrOTDudqtcEUGa6yFXod13B5FyQJkcsGM7x7LcwG
14+
e528NR36t83qcinzJI/WCZMSAzw0l2MlVl/bMmbhvtjjalNSX2OV7ezhfWd9xop0
15+
X+p3Drurdc8b39igIrCPfErdj/gPcBc=
16+
-----END CERTIFICATE REQUEST-----

0 commit comments

Comments
 (0)