Skip to content

Commit c1d84de

Browse files
committed
initial implementation in nodejs
1 parent d38f1f9 commit c1d84de

10 files changed

+262
-0
lines changed

.dockerignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
env_var_requirements.txt
2+
/node_modules
3+
.gitignore
4+
README.md
5+
*.log

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/node_modules/
2+
*.log
3+
*.env*
4+
*.pem
5+

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# node-kubernetes-github-authn
2+
3+
Kubernetes Webhook RBAC authentication service written in NodeJS to run as a daemon set across cluster master nodes.
4+
5+
- Handles RBAC authentication requests from K8s by checking if the user (token) is valid and exists for a user in Github with the username associated against any relevant Kubernetes rolebindings or clusterrolebinding mappings.
6+
7+
## Install and run local dev
8+
9+
- npm install (to install required npm packages)
10+
- npm install -g nodemon (to install nodemon)
11+
- nodemon (from the repo directory to launch node and invoke server.js) - this is better for local dev iteration than running ```node server.js``` as it will restart the app every time you make changes to files
12+
13+
## Docker image building
14+
15+
From the root directory of the project (i.e. where the .dockerignore file is, as well as server.js), run:
16+
17+
- docker build -t node-kubernetes-github-authn:{VERSION TAG} -f .\docker\Dockerfile .
18+
19+
This will build the Dockerfile in the context of the root project folder, so that the COPY command in the Dockerfile copies in the files from this level (locally).
20+
21+
## Docker run local dev
22+
23+
docker run node-kubernetes-github-authn:{VERSION TAG}/latest
24+
25+
## Pull image down from the public Docker registry
26+
27+
Pull the image down using: shoganator/kubernetes-github-authn-node
28+
29+
## AWS ECR push Docker images
30+
31+
If you want to build and push up to ECR (e.g. for private K8s clusters that don't have access to public docker registry) you can use the following:
32+
33+
- Invoke-Expression -Command (aws ecr get-login --no-include-email --region us-east-1)
34+
- docker build -t node-kubernetes-github-authn -f .\docker\Dockerfile .
35+
- docker tag node-kubernetes-github-authn:latest yourawsaccountnumber.dkr.ecr.us-east-1.amazonaws.com/node-kubernetes-github-authn:latest
36+
- docker tag node-kubernetes-github-authn:{VERSION TAG} yourawsaccountnumber.dkr.ecr.us-east-1.amazonaws.com/node-kubernetes-github-authn:{VERSION TAG}
37+
- docker push yourawsaccountnumber.dkr.ecr.us-east-1.amazonaws.com/node-kubernetes-github-authn:latest
38+
- docker push yourawsaccountnumber.dkr.ecr.us-east-1.amazonaws.com/node-kubernetes-github-authn:{VERSION TAG}
39+
40+
## Container logging
41+
42+
Winston is configured to write logs to both the console of the container as well as to a rolling log file under /logs. Please ensure your docker container build includes the /logs directory.

docker/Dockerfile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM node:9.5.0-alpine
2+
3+
RUN mkdir -p /usr/src/node-kubernetes-github-authn
4+
WORKDIR /usr/src/node-kubernetes-github-authn
5+
COPY . /usr/src/node-kubernetes-github-authn
6+
7+
# If you have native dependencies, you'll need extra tools
8+
# RUN apk add --no-cache make gcc g++ python
9+
10+
RUN npm install --production
11+
EXPOSE 3000
12+
13+
CMD ["node", "server.js"]

k8s-artifacts/daemonset.yaml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
apiVersion: extensions/v1beta1
2+
kind: DaemonSet
3+
metadata:
4+
labels:
5+
k8s-app: github-authn-node
6+
name: github-authn-node
7+
namespace: kube-system
8+
spec:
9+
selector:
10+
matchLabels:
11+
k8s-app: github-authn-node
12+
template:
13+
metadata:
14+
labels:
15+
k8s-app: github-authn-node
16+
annotations:
17+
scheduler.alpha.kubernetes.io/critical-pod: ''
18+
spec:
19+
containers:
20+
- image: shoganator/kubernetes-github-authn-node
21+
name: kubernetes-github-authn-node
22+
ports:
23+
- containerPort: 3000
24+
hostPort: 3000
25+
protocol: TCP
26+
hostNetwork: true
27+
tolerations:
28+
- key: node-role.kubernetes.io/master
29+
effect: NoSchedule
30+
nodeSelector:
31+
node-role.kubernetes.io/master: ""
32+
restartPolicy: Always
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"kind": "Config",
3+
"apiVersion": "v1",
4+
"preferences": {},
5+
"clusters": [
6+
{
7+
"name": "github-authn-node",
8+
"cluster": {
9+
"server": "http://localhost:3000/authenticate"
10+
}
11+
}
12+
],
13+
"users": [
14+
{
15+
"name": "authn-apiserver",
16+
"user": {
17+
"token": "secret"
18+
}
19+
}
20+
],
21+
"contexts": [
22+
{
23+
"name": "webhook",
24+
"context": {
25+
"cluster": "github-authn-node",
26+
"user": "authn-apiserver"
27+
}
28+
}
29+
],
30+
"current-context": "webhook"
31+
}

logs/winston-logs-go-here.txt

Whitespace-only changes.

modules/logger.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var winston = require('winston')
2+
require('winston-daily-rotate-file')
3+
4+
var consoleTransport = new winston.transports.Console({
5+
timestamp: true,
6+
colorize: true
7+
})
8+
9+
var dailyRotateTransport = new (winston.transports.DailyRotateFile)({
10+
filename: './logs/node-kubernetes-github-authn.log',
11+
datePattern: 'yyyy-MM-dd.',
12+
localTime: false,
13+
prepend: true,
14+
maxDays: 180,
15+
level: 'info'
16+
})
17+
18+
var logger = new (winston.Logger)({
19+
transports: [
20+
consoleTransport,
21+
dailyRotateTransport
22+
]
23+
})
24+
25+
module.exports = logger

package.json

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "node-kubernetes-github-authn",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "server.js",
6+
"dependencies": {
7+
"body-parser": "^1.18.2",
8+
"chump": "^1.2.0",
9+
"express": "^4.16.2",
10+
"express-validator": "^4.3.0",
11+
"kubernetes-client": "^4.0.0",
12+
"octonode": "0.9.2",
13+
"request": "^2.87.0",
14+
"winston": "^2.4.0",
15+
"winston-daily-rotate-file": "^1.7.2"
16+
},
17+
"devDependencies": {},
18+
"scripts": {
19+
"test": "echo \"Error: no test specified\" && exit 1",
20+
"start": "node server.js"
21+
},
22+
"author": "Sean Duffy",
23+
"license": "ISC"
24+
}

server.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"use strict";
2+
3+
const express = require("express")
4+
const bodyParser = require('body-parser')
5+
const expressValidator = require('express-validator')
6+
const github = require('octonode')
7+
const logger = require('./modules/logger')
8+
const port = 3000
9+
10+
let app = express();
11+
12+
// Setup various middlewares
13+
app.use(bodyParser.urlencoded({ extended: false}))
14+
app.use(bodyParser.json())
15+
app.use(expressValidator({
16+
errorFormatter: function(param, msg, value) {
17+
var namespace = param.split('.')
18+
, root = namespace.shift()
19+
, formParam = root
20+
21+
while(namespace.length) {
22+
formParam += '[' + namespace.shift() + ']'
23+
}
24+
return {
25+
param : formParam,
26+
msg : msg,
27+
value: value
28+
}
29+
}
30+
}))
31+
32+
// Setup application routes
33+
app.get('/', (req, res) => {
34+
logger.info('ping')
35+
res.sendStatus(200)
36+
})
37+
38+
app.post('/authenticate', (req, res) => {
39+
let postData = req.body
40+
if (postData === null || postData === 'undefined') {
41+
let responseJson = {
42+
'apiVersion': 'authentication.k8s.io/v1beta1',
43+
'kind': 'TokenReview',
44+
'status': {
45+
'Authenticated': false
46+
}
47+
}
48+
res.status(401).send(responseJson)
49+
} else {
50+
51+
let token = postData.spec.token
52+
let client = github.client(token)
53+
client.get('/user', {}, function (err, status, body, headers) {
54+
if (err) {
55+
logger.error('could not retrieve user with the token passed in.', err)
56+
let responseJson = {
57+
'apiVersion': 'authentication.k8s.io/v1beta1',
58+
'kind': 'TokenReview',
59+
'status': {
60+
'Authenticated': false
61+
}
62+
}
63+
res.status(401).send(responseJson)
64+
} else {
65+
logger.info('authenticated OK with github for user: ' + body.login)
66+
let responseJson = {
67+
'apiVersion': 'authentication.k8s.io/v1beta1',
68+
'kind': 'TokenReview',
69+
'status': {
70+
'Authenticated': true,
71+
'User': {
72+
'Username': body.login,
73+
'UID': body.login
74+
// Potentially in the future get user team membership from github, and pass into Groups[] here...
75+
}
76+
}
77+
}
78+
res.status(200).send(responseJson)
79+
}
80+
})
81+
}
82+
})
83+
84+
app.listen(port);
85+
logger.info('Application started and listening on port ' + port)

0 commit comments

Comments
 (0)