Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support APIKey authentication #5580

Merged
merged 44 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ba8f0c2
always load njs module
haywoodsh May 9, 2024
d046e57
accept api key policy yaml
haywoodsh May 20, 2024
94851fc
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 20, 2024
20f7d06
add njs and nginx config
May 20, 2024
fd33e3b
move js import to http
May 22, 2024
5ce5ac5
update schema and allow update after NIC starts
haywoodsh May 24, 2024
4d2ee9d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2024
c529d8a
remove hardcoded variable
May 24, 2024
7a579a9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2024
30c019d
add updated njs
May 28, 2024
2643320
move js set outside location
May 28, 2024
c4b0bdf
use nginx.org/apikey secret type
haywoodsh May 29, 2024
be1b05e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 29, 2024
2a7f66a
simplify njs
May 31, 2024
083df06
make query params work
May 31, 2024
512e27b
clean up template files
May 31, 2024
573be80
add api key secret validation to reject duplicated keys, remove repea…
haywoodsh Jun 6, 2024
5a8916b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 6, 2024
8f877e3
fix linting, remove unused structs, update crds and codegen
haywoodsh Jun 6, 2024
1d91b7c
add unit tests, unique map names, add validate apikey location block …
haywoodsh Jun 11, 2024
3e53c4b
add python tests for vs and vsr
Jun 12, 2024
160a3b4
Merge branch 'main' into feat/api-key
Jun 12, 2024
b49634f
Merge branch 'main' into feat/api-key
Jun 12, 2024
028ff04
fix dockerfile merge
Jun 12, 2024
93a1e0b
add wait until pods are ready
Jun 12, 2024
3ac68d4
update error message
haywoodsh Jun 12, 2024
8c1d9d1
test setting same namespace
Jun 12, 2024
b4d382f
Merge remote-tracking branch 'refs/remotes/origin/feat/api-key' into …
Jun 12, 2024
9682420
custom objects
Jun 12, 2024
48f8054
add crd print
Jun 13, 2024
99ba140
add unit tests
haywoodsh Jun 13, 2024
1f50525
Add example readme for apikey auth policy
Jun 13, 2024
cf4fb2c
Merge branch 'main' into feat/api-key
Jun 13, 2024
e9a0a75
clean up
Jun 13, 2024
21be26e
Merge remote-tracking branch 'refs/remotes/origin/feat/api-key' into …
Jun 13, 2024
9806d6b
further cleanup
Jun 13, 2024
8956744
clean up test
Jun 13, 2024
fff5d6d
add unit tests, clean up code
haywoodsh Jun 13, 2024
b983616
remove logs, refactor, add tests
haywoodsh Jun 14, 2024
ec753ce
remove logs
haywoodsh Jun 14, 2024
ec84a05
Merge branch 'main' into feat/api-key
Jun 14, 2024
c6bd588
add api key auth to telemetry
Jun 14, 2024
d415866
Merge branch 'main' into feat/api-key
Jun 14, 2024
fec5a3b
Merge branch 'main' into feat/api-key
Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ if [ -z "${BUILD_OS##*plus*}" ]; then
PLUS=-plus
fi

mkdir -p /etc/nginx/njs/ && cp -a /tmp/internal/configs/njs/* /etc/nginx/njs/
mkdir -p /var/lib/nginx /etc/nginx/secrets /etc/nginx/stream-conf.d
setcap 'cap_net_bind_service=+eip' /usr/sbin/nginx 'cap_net_bind_service=+eip' /usr/sbin/nginx-debug
setcap -v 'cap_net_bind_service=+eip' /usr/sbin/nginx 'cap_net_bind_service=+eip' /usr/sbin/nginx-debug
Expand Down
19 changes: 19 additions & 0 deletions config/crd/bases/k8s.nginx.org_policies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ spec:
type: string
type: array
type: object
apiKey:
description: APIKey defines an API Key policy.
properties:
clientSecret:
type: string
suppliedIn:
description: SuppliedIn defines the locations API Key should be
supplied in.
properties:
header:
items:
type: string
type: array
query:
items:
type: string
type: array
type: object
type: object
basicAuth:
description: |-
BasicAuth holds HTTP Basic authentication configuration
Expand Down
19 changes: 19 additions & 0 deletions deploy/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,25 @@ spec:
type: string
type: array
type: object
apiKey:
description: APIKey defines an API Key policy.
properties:
clientSecret:
type: string
suppliedIn:
description: SuppliedIn defines the locations API Key should be
supplied in.
properties:
header:
items:
type: string
type: array
query:
items:
type: string
type: array
type: object
type: object
basicAuth:
description: |-
BasicAuth holds HTTP Basic authentication configuration
Expand Down
122 changes: 122 additions & 0 deletions examples/custom-resources/api-key/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# API Key Authentication

NGINX supports authenticating requests with
[ngx_http_auth_request_module](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html). In this example, we deploy
a web application, configure load balancing for it via a VirtualServer, and apply an API Key Auth policy.

## Prerequisites

1. Follow the [installation](https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-manifests/)
instructions to deploy the Ingress Controller. In this example we will be using a snippet to turn the policy off on a specific path so ensure that the `enable-snippets` flag is set.
1. Save the public IP address of the Ingress Controller into a shell variable:

```console
IC_IP=XXX.YYY.ZZZ.III
```

1. Save the HTTP port of the Ingress Controller into a shell variable:

```console
IC_HTTP_PORT=<port number>
```

## Step 1 - Deploy a Web Application

Create the application deployment and service:

```console
kubectl apply -f cafe.yaml
```

## Step 2 - Deploy the API Key Auth Secret

Create a secret of type `nginx.org/apikey` with the name `api-key-client-secret` that will be used for authorization on the server level.

This secret will contain a mapping of client IDs to base64 encoded API Keys.

```console
kubectl apply -f api-key-secret.yaml
```

## Step 3 - Deploy the API Key Auth Policy

Create a policy with the name `api-key-policy` that references the secret from the previous step in the clientSecret field.
Provide an array of headers and queries in the header and query fields of the suppliedIn field, indicating where the API key can be sent

```console
kubectl apply -f api-key-policy.yaml
```

## Step 4 - Configure Load Balancing

Create a VirtualServer resource for the web application:

```console
kubectl apply -f cafe-virtual-server.yaml
```

Note that the VirtualServer references the policy `api-key-policy` created in Step 3.

## Step 5 - Test the Configuration

If you attempt to access the application without providing a valid API Key in a expected header or query param for that VirtualServer:

```console
curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP http://cafe.example.com:$IC_HTTP_PORT/
```

```text
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.21.5</center>
</body>
</html>
```

If you attempt to access the application providing an incorrect API Key in an expected header or query param for that VirtualServer:

```console
curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP -H "X-header-name: wrongpassword" http://cafe.example.com:$IC_HTTP_PORT/coffee
```

```text
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.27.0</center>
</body>
</html>
```

If you provide a valid API Key in an a header or query defined in the policy, your request will succeed:

```console
curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP -H "X-header-name: password" https://cafe.example.com:$IC_HTTPS_PORT/coffee
```

```text
Server address: 10.244.0.6:8080
Server name: coffee-56b44d4c55-vjwxd
Date: 13/Jun/2024:13:12:17 +0000
URI: /coffee
Request ID: 4feedb3265a0430a1f58831d016e846d
```

If you attempt to access the /tea path, the request will be allowed without an API Key, because the auth_request directive is turned off for that path with a location snippet:

```console
curl --resolve cafe.example.com:$IC_HTTP_PORT:$IC_IP http://cafe.example.com:$IC_HTTP_PORT/tea
```

```text
Server address: 10.244.0.5:8080
Server name: tea-596697966f-dmq7t
Date: 13/Jun/2024:13:16:46 +0000
URI: /tea
Request ID: 26e6d7dd0272eca82f31f33bf90698c9
```

Additionally you can set [error pages](https://docs.nginx.com/nginx-ingress-controller/configuration/virtualserver-and-virtualserverroute-resources/#errorpage) to handle the 401 and 403 responses.
12 changes: 12 additions & 0 deletions examples/custom-resources/api-key/api-key-policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: api-key-policy
spec:
apiKey:
suppliedIn:
header:
- "X-header-name"
query:
- "queryName"
clientSecret: api-key-client-secret
8 changes: 8 additions & 0 deletions examples/custom-resources/api-key/api-key-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: api-key-client-secret
type: nginx.org/apikey
data:
client1: cGFzc3dvcmQ= # password
client2: YW5vdGhlci1wYXNzd29yZA== # another-password
8 changes: 8 additions & 0 deletions examples/custom-resources/api-key/cafe-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: cafe-secret
type: kubernetes.io/tls
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURMakNDQWhZQ0NRREFPRjl0THNhWFdqQU5CZ2txaGtpRzl3MEJBUXNGQURCYU1Rc3dDUVlEVlFRR0V3SlYKVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhJVEFmQmdOVkJBb01HRWx1ZEdWeWJtVjBJRmRwWkdkcGRITWdVSFI1SUV4MApaREViTUJrR0ExVUVBd3dTWTJGbVpTNWxlR0Z0Y0d4bExtTnZiU0FnTUI0WERURTRNRGt4TWpFMk1UVXpOVm9YCkRUSXpNRGt4TVRFMk1UVXpOVm93V0RFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVNFd0h3WUQKVlFRS0RCaEpiblJsY201bGRDQlhhV1JuYVhSeklGQjBlU0JNZEdReEdUQVhCZ05WQkFNTUVHTmhabVV1WlhoaApiWEJzWlM1amIyMHdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFDcDZLbjdzeTgxCnAwanVKL2N5ayt2Q0FtbHNmanRGTTJtdVpOSzBLdGVjcUcyZmpXUWI1NXhRMVlGQTJYT1N3SEFZdlNkd0kyaloKcnVXOHFYWENMMnJiNENaQ0Z4d3BWRUNyY3hkam0zdGVWaVJYVnNZSW1tSkhQUFN5UWdwaW9iczl4N0RsTGM2SQpCQTBaalVPeWwwUHFHOVNKZXhNVjczV0lJYTVyRFZTRjJyNGtTa2JBajREY2o3TFhlRmxWWEgySTVYd1hDcHRDCm42N0pDZzQyZitrOHdnemNSVnA4WFprWldaVmp3cTlSVUtEWG1GQjJZeU4xWEVXZFowZXdSdUtZVUpsc202OTIKc2tPcktRajB2a29QbjQxRUUvK1RhVkVwcUxUUm9VWTNyemc3RGtkemZkQml6Rk8yZHNQTkZ4MkNXMGpYa05MdgpLbzI1Q1pyT2hYQUhBZ01CQUFFd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFLSEZDY3lPalp2b0hzd1VCTWRMClJkSEliMzgzcFdGeW5acS9MdVVvdnNWQTU4QjBDZzdCRWZ5NXZXVlZycTVSSWt2NGxaODFOMjl4MjFkMUpINnIKalNuUXgrRFhDTy9USkVWNWxTQ1VwSUd6RVVZYVVQZ1J5anNNL05VZENKOHVIVmhaSitTNkZBK0NuT0Q5cm4yaQpaQmVQQ0k1ckh3RVh3bm5sOHl3aWozdnZRNXpISXV5QmdsV3IvUXl1aTlmalBwd1dVdlVtNG52NVNNRzl6Q1Y3ClBwdXd2dWF0cWpPMTIwOEJqZkUvY1pISWc4SHc5bXZXOXg5QytJUU1JTURFN2IvZzZPY0s3TEdUTHdsRnh2QTgKN1dqRWVxdW5heUlwaE1oS1JYVmYxTjM0OWVOOThFejM4Zk9USFRQYmRKakZBL1BjQytHeW1lK2lHdDVPUWRGaAp5UkU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBcWVpcCs3TXZOYWRJN2lmM01wUHJ3Z0pwYkg0N1JUTnBybVRTdENyWG5LaHRuNDFrCkcrZWNVTldCUU5semtzQndHTDBuY0NObzJhN2x2S2wxd2k5cTIrQW1RaGNjS1ZSQXEzTVhZNXQ3WGxZa1YxYkcKQ0pwaVJ6ejBza0lLWXFHN1BjZXc1UzNPaUFRTkdZMURzcGRENmh2VWlYc1RGZTkxaUNHdWF3MVVoZHErSkVwRwp3SStBM0kreTEzaFpWVng5aU9WOEZ3cWJRcCt1eVFvT05uL3BQTUlNM0VWYWZGMlpHVm1WWThLdlVWQ2cxNWhRCmRtTWpkVnhGbldkSHNFYmltRkNaYkp1dmRySkRxeWtJOUw1S0Q1K05SQlAvazJsUkthaTAwYUZHTjY4NE93NUgKYzMzUVlzeFR0bmJEelJjZGdsdEkxNURTN3lxTnVRbWF6b1Z3QndJREFRQUJBb0lCQVFDUFNkU1luUXRTUHlxbApGZlZGcFRPc29PWVJoZjhzSStpYkZ4SU91UmF1V2VoaEp4ZG01Uk9ScEF6bUNMeUw1VmhqdEptZTIyM2dMcncyCk45OUVqVUtiL1ZPbVp1RHNCYzZvQ0Y2UU5SNThkejhjbk9SVGV3Y290c0pSMXBuMWhobG5SNUhxSkpCSmFzazEKWkVuVVFmY1hackw5NGxvOUpIM0UrVXFqbzFGRnM4eHhFOHdvUEJxalpzVjdwUlVaZ0MzTGh4bndMU0V4eUZvNApjeGI5U09HNU9tQUpvelN0Rm9RMkdKT2VzOHJKNXFmZHZ5dGdnOXhiTGFRTC94MGtwUTYyQm9GTUJEZHFPZVBXCktmUDV6WjYvMDcvdnBqNDh5QTFRMzJQem9idWJzQkxkM0tjbjMyamZtMUU3cHJ0V2wrSmVPRmlPem5CUUZKYk4KNHFQVlJ6NWhBb0dCQU50V3l4aE5DU0x1NFArWGdLeWNrbGpKNkY1NjY4Zk5qNUN6Z0ZScUowOXpuMFRsc05ybwpGVExaY3hEcW5SM0hQWU00MkpFUmgySi9xREZaeW5SUW8zY2czb2VpdlVkQlZHWTgrRkkxVzBxZHViL0w5K3l1CmVkT1pUUTVYbUdHcDZyNmpleHltY0ppbS9Pc0IzWm5ZT3BPcmxEN1NQbUJ2ek5MazRNRjZneGJYQW9HQkFNWk8KMHA2SGJCbWNQMHRqRlhmY0tFNzdJbUxtMHNBRzR1SG9VeDBlUGovMnFyblRuT0JCTkU0TXZnRHVUSnp5K2NhVQprOFJxbWRIQ2JIelRlNmZ6WXEvOWl0OHNaNzdLVk4xcWtiSWN1YytSVHhBOW5OaDFUanNSbmU3NFowajFGQ0xrCmhIY3FIMHJpN1BZU0tIVEU4RnZGQ3haWWRidUI4NENtWmlodnhicFJBb0dBSWJqcWFNWVBUWXVrbENkYTVTNzkKWVNGSjFKelplMUtqYS8vdER3MXpGY2dWQ0thMzFqQXdjaXowZi9sU1JxM0hTMUdHR21lemhQVlRpcUxmZVpxYwpSMGlLYmhnYk9jVlZrSkozSzB5QXlLd1BUdW14S0haNnpJbVpTMGMwYW0rUlk5WUdxNVQ3WXJ6cHpjZnZwaU9VCmZmZTNSeUZUN2NmQ21mb09oREN0enVrQ2dZQjMwb0xDMVJMRk9ycW40M3ZDUzUxemM1em9ZNDR1QnpzcHd3WU4KVHd2UC9FeFdNZjNWSnJEakJDSCtULzZzeXNlUGJKRUltbHpNK0l3eXRGcEFOZmlJWEV0LzQ4WGY2ME54OGdXTQp1SHl4Wlp4L05LdER3MFY4dlgxUE9ucTJBNWVpS2ErOGpSQVJZS0pMWU5kZkR1d29seHZHNmJaaGtQaS80RXRUCjNZMThzUUtCZ0h0S2JrKzdsTkpWZXN3WEU1Y1VHNkVEVXNEZS8yVWE3ZlhwN0ZjanFCRW9hcDFMU3crNlRYcDAKWmdybUtFOEFSek00NytFSkhVdmlpcS9udXBFMTVnMGtKVzNzeWhwVTl6WkxPN2x0QjBLSWtPOVpSY21Vam84UQpjcExsSE1BcWJMSjhXWUdKQ2toaVd4eWFsNmhZVHlXWTRjVmtDMHh0VGwvaFVFOUllTktvCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
26 changes: 26 additions & 0 deletions examples/custom-resources/api-key/cafe-virtual-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: cafe
spec:
host: cafe.example.com
tls:
secret: cafe-secret
policies:
- name: api-key-policy
upstreams:
- name: coffee
service: coffee-svc
port: 80
- name: tea
service: tea-svc
port: 80
routes:
- path: /coffee
action:
pass: coffee
- path: /tea
location-snippets: |
auth_request off;
action:
pass: tea
65 changes: 65 additions & 0 deletions examples/custom-resources/api-key/cafe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: coffee
spec:
replicas: 2
selector:
matchLabels:
app: coffee
template:
metadata:
labels:
app: coffee
spec:
containers:
- name: coffee
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: coffee-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: coffee
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tea
spec:
replicas: 1
selector:
matchLabels:
app: tea
template:
metadata:
labels:
app: tea
spec:
containers:
- name: tea
image: nginxdemos/nginx-hello:plain-text
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: tea-svc
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: tea
3 changes: 3 additions & 0 deletions internal/configs/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1893,6 +1893,9 @@ func (cnf *Configurator) AddOrUpdateSecret(secret *api_v1.Secret) string {
case secrets.SecretTypeOIDC:
// OIDC ClientSecret is not required on the filesystem, it is written directly to the config file.
return ""
case secrets.SecretTypeAPIKey:
// APIKey ClientSecret is not required on the filesystem, it is written directly to the config file.
return ""
default:
return cnf.addOrUpdateTLSSecret(secret)
}
Expand Down
26 changes: 26 additions & 0 deletions internal/configs/njs/apikey_auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const c = require('crypto')

function hash(r) {
const header_query_value = r.variables.header_query_value;
const hashed_value = c.createHash('sha256').update(header_query_value).digest('hex');
return hashed_value;
}

function validate(r) {
const client_name_map = r.variables['apikey_auth_local_map'];
const client_name = r.variables[client_name_map];
const header_query_value = r.variables.header_query_value;

if (!header_query_value) {
r.return(401, "401")
}
else if (!client_name) {
r.return(403, "403")
}
else {
r.return(204, "204");
}

}

export default { validate, hash };
5 changes: 3 additions & 2 deletions internal/configs/version1/nginx-plus.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ load_module modules/ngx_fips_check_module.so;
{{$value}}{{end}}
{{- end}}

{{- if .OIDC}}
load_module modules/ngx_http_js_module.so;
{{- end}}
oseoin marked this conversation as resolved.
Show resolved Hide resolved

events {
worker_connections {{.WorkerConnections}};
Expand All @@ -41,6 +39,9 @@ http {
map_hash_max_size {{.MapHashMaxSize}};
map_hash_bucket_size {{.MapHashBucketSize}};

js_import /etc/nginx/njs/apikey_auth.js;
js_set $apikey_auth_hash apikey_auth.hash;

{{- if .HTTPSnippets}}
{{range $value := .HTTPSnippets}}
{{$value}}{{end}}
Expand Down
6 changes: 6 additions & 0 deletions internal/configs/version1/nginx.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ load_module modules/ngx_http_opentracing_module.so;
{{$value}}{{end}}
{{- end}}

load_module modules/ngx_http_js_module.so;

events {
worker_connections {{.WorkerConnections}};
}
Expand All @@ -30,6 +32,10 @@ http {
map_hash_max_size {{.MapHashMaxSize}};
map_hash_bucket_size {{.MapHashBucketSize}};


js_import /etc/nginx/njs/apikey_auth.js;
js_set $apikey_auth_hash apikey_auth.hash;

{{- if .HTTPSnippets}}
{{range $value := .HTTPSnippets}}
{{$value}}{{end}}
Expand Down
Loading
Loading