Skip to content

Commit

Permalink
Merge pull request #25 from gyselroth/dev
Browse files Browse the repository at this point in the history
version 2.0.0
  • Loading branch information
juckerf authored Jun 12, 2019
2 parents f2fcf73 + 0c2cede commit 7809f1f
Show file tree
Hide file tree
Showing 20 changed files with 549 additions and 483 deletions.
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.editorconfig
.build
build
node_modules
.git
Dockerfile
19 changes: 18 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.0.0] - 2019-06-12
### Added
- Prometheus exporter on route "/metrics" (basic auth protected)

### Changed
- **BREAKING:** Extra-Attributes and groups are now no longer included in the JWT issued after user authentication. Extra-Attributes and group memberships are now resolved during the token review and are included in the token review response
- Internal: Use [ldapts](https://github.com/ldapts/ldapts) instead of [ldapjs](https://github.com/joyent/node-ldapjs) as ldap library

### Fixed
- Fix membership resolution for ldap objects without any membership

### Removed
- **BREAKING:** LDAP StartTLS is no longer supported
- **BREAKING:** LDAP reconnect logic (now there's a new connection for every request)

## [1.3.0] - 2019-01-07
### Changed
- Failed authentication sends a WWW-Authenticate header in the HTTP response
Expand Down Expand Up @@ -40,7 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial key functionality

[Unreleased]: https://github.com/gyselroth/kube-ldap/compare/v1.3.0...master
[Unreleased]: https://github.com/gyselroth/kube-ldap/compare/v2.0.0...master
[2.0.0]: https://github.com/gyselroth/kube-ldap/compare/v1.3.0...v2.0.0
[1.3.0]: https://github.com/gyselroth/kube-ldap/compare/v1.2.1...v1.3.0
[1.3.0]: https://github.com/gyselroth/kube-ldap/compare/v1.2.1...v1.3.0
[1.2.1]: https://github.com/gyselroth/kube-ldap/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/gyselroth/kube-ldap/compare/v1.1.0...v1.2.0
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:8.15.0-alpine
FROM node:8.16.0-alpine

RUN apk --no-cache add ca-certificates wget && \
wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
Expand Down
13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ A [Webhook Token Authentication](https://kubernetes.io/docs/admin/authentication
The kube-ldap webhook token authentication plugin can be used to integrate username/password authentication via LDAP for your kubernetes cluster.
It exposes two API endpoints:
* /auth
* HTTP basic authenticated requests to this endpoint result in a JSON Web Token, signed by the webhook, including the username, uid and group memberships of the authenticated user.
* The issued token can be used for authenticating to kubernetes.
* HTTP basic authenticated requests to this endpoint result in a JSON Web Token, signed by the webhook, including the username and uid of the authenticated user.
* The issued token can be used for authenticating to kubernetes.
* /token
* Is called by kubernetes (see [TokenReview](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#tokenreview-v1-authentication)) to verify the token used for authentication.
* Verifies the integrity of the JWT (using the signature) and returns a TokenReview response containing the username, uid and group memberships of the authenticated user.
* Verifies the integrity of the JWT (using the signature) and returns a TokenReview response containing the username, uid, group memberships and extra attributes (if configured) of the authenticated user.

## Deployment
The recommended way to deploy kube-ldap is deplyoing kube-ldap in kubernetes itself using the [gyselroth/kube-ldap](https://hub.docker.com/r/gyselroth/kube-ldap/) docker image.
Expand Down Expand Up @@ -150,17 +150,16 @@ List of configurable values:
|`config.ldap.baseDn`|Base DN for LDAP search|`LDAP_BASEDN`|dc=example,dc=com|
|`config.ldap.filter`|Filter for LDAP search|`LDAP_FILTER`|(uid=%s)|
|`config.ldap.timeout`|Timeout for LDAP connections & operations (in seconds)|`LDAP_TIMEOUT`|0 (infinite for operations, OS default for connections)|
|`config.ldap.reconnectInitialDelay`|Milliseconds to wait before reconnecting|`LDAP_RECONN_INIT_DELAY`|100|
|`config.ldap.reconnectMaxDelay`|Maximum milliseconds to wait before reconnecting|`LDAP_RECONN_MAX_DELAY`|1000|
|`config.ldap.reconnectFailAfter`|Fail after number of retries|`LDAP_RECONN_FAIL_AFTER`|10|
|`config.ldap.startTls`|Whether to use StartTLS for the LDAP connection or not|`LDAP_STARTTLS`|true|
|`config.mapping.username`|Name of ldap attribute to be used as username in kubernetes TokenReview|`MAPPING_USERNAME`|uid|
|`config.mapping.uid`|Name of ldap attribute to be used as uid in kubernetes TokenReview|`MAPPING_UID`|uid|
|`config.mapping.groups`|Name of ldap attribute to be used for groups in kubernetes TokenReview|`MAPPING_GROUPS`|memberOf|
|`config.mapping.extraFields`|Comma separated list of additional ldap attributes to be used for extra in kubernetes TokenReview|`MAPPING_EXTRAFIELDS`|[]|
|`config.mapping.username`|Name of Ldap attribute to be used as username in kubernetes TokenReview|`MAPPING_USERNAME`|uid|
|`config.jwt.key`|Key for signing the JWT. **DO NOT USE THE DEFAULT VALUE IN PRODUCTION**|`JWT_KEY`|secret|
|`config.jwt.tokenLifetime`|Seconds until token a expires|`JWT_TOKEN_LIFETIME`|28800|
|`config.prometheus.username`|Username for prometheus exporter basic auth (use empty string to disable basic auth)|`PROMETHEUS_USERNAME`|prometheus|
|`config.prometheus.password`|Password for prometheus exporter basic auth (use empty string to disable basic auth)|`PROMETHEUS_PASSWORD`|secret|
|`config.prometheus.nodejsProbeInterval`|Probe interval for nodejs metrics in milliseconds|`PROMETHEUS_NODEJS_PROBE_INTERVAL`|10000|

### kubernetes
Configure your kubernetes apiserver to use the kube-ldap [webhook for authentication](https://kubernetes.io/docs/admin/authentication/#webhook-token-authentication) using the following configuration file.
Expand Down
85 changes: 0 additions & 85 deletions __mocks__/ldapjs.js

This file was deleted.

48 changes: 48 additions & 0 deletions __mocks__/ldapts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// @flow
declare var jest: any;

let staticMock = null;
/** Mock of Authenticator class */
class LdapMock {
bindReturnsError: boolean;
searchReturnsError: boolean;
searchReturnsResult: boolean;
searchResult: Object;
bind: (string, Array<string>) => Promise<Object>;
unbind: () => void;
search: (string, Array<string>) => Promise<Object>;
on: (string, (any) => any) => void;

/** creates the mock */
constructor() {
if (staticMock) {
return staticMock;
}
staticMock = this;
this.bindReturnsError = false;
this.searchReturnsError = false;
this.searchReturnsResult = true;
this.searchResult = {};
this.bind = jest.fn();
this.bind.mockImplementation((dn, password, controls) => {
if (this.bindReturnsError) {
throw new Error('error by mock');
} else {
return null;
}
});
this.unbind = jest.fn();
this.search = jest.fn();
this.search.mockImplementation((base, options, controls) => {
if (this.searchReturnsError) {
throw new Error('error by mock');
} else {
return {
searchEntries: this.searchReturnsResult ? [this.searchResult] : [],
};
}
});
}
}

export {LdapMock as Client};
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kube-ldap",
"version": "1.3.0",
"version": "2.0.0",
"description": "kubernetes token webhook to check bearer tokens against ldap",
"main": "src/index.js",
"author": "Fabian Jucker <[email protected]>",
Expand All @@ -12,9 +12,12 @@
"bunyan-winston-adapter": "^0.2.0",
"cors": "^2.8.4",
"express": "^4.16.3",
"express-basic-auth": "^1.2.0",
"express-prom-bundle": "^5.1.5",
"jsonwebtoken": "^8.2.0",
"ldapjs": "^1.0.2",
"ldapts": "^1.7.0",
"morgan": "^1.9.0",
"prom-client": "^11.5.0",
"winston": "^2.4.1"
},
"devDependencies": {
Expand Down
68 changes: 49 additions & 19 deletions src/api/tokenAuthentication.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
// @flow
import {Logger} from 'winston';
import jwt from 'jsonwebtoken';
import {Authenticator, Mapping} from '../ldap';

/** Class for TokenAuthentication API route */
export default class TokenAuthentication {
authenticator: Authenticator;
mapping: Mapping;
key: string;
logger: Logger;
run: (Object, Object) => void
extractAndVerifyToken: (string) => Object

/**
* Create API route
* @param {Authenticator} authenticator - Authenticator.
* @param {Mapping} mapping - Attribute mapping (kubernetes<=>ldap).
* @param {string} key - Private key.
* @param {Logger} logger - Logger to use.
*/
constructor(key: string, logger: Logger) {
constructor(
authenticator: Authenticator,
mapping: Mapping,
key: string,
logger: Logger
) {
this.authenticator = authenticator;
this.mapping = mapping;
this.key = key;
this.logger = logger;
this.run = this.run.bind(this);
Expand All @@ -26,7 +38,7 @@ export default class TokenAuthentication {
* @param {Object} req - Request.
* @param {Object} res - Response.
*/
run(req: Object, res: Object) {
async run(req: Object, res: Object): Promise<void> {
if (
!req.body.apiVersion ||
!req.body.kind ||
Expand All @@ -40,30 +52,48 @@ export default class TokenAuthentication {
) {
res.sendStatus(400);
} else {
let responseData = {
apiVersion: 'authentication.k8s.io/v1beta1',
kind: 'TokenReview',
status: {
authenticated: false,
user: {},
},
};

let token =req.body.spec.token;
try {
let data = this.extractAndVerifyToken(token);
responseData.status.user = data;
responseData.status.authenticated = true;
let responseData = await this._processToken(token);
res.send(responseData);
} catch (error) {
delete responseData.status.user;
responseData.status.authenticated = false;
this.logger.info('Error while verifying token: ' +
`[${error.name}] with message [${error.message}]`);
this.logger.error(error);
res.sendStatus(500);
}
res.send(responseData);
}
}

/**
* Process token
* @param {string} token - The token.
* @return {Object}
*/
async _processToken(token: string): Promise<Object> {
let responseData = {
apiVersion: 'authentication.k8s.io/v1beta1',
kind: 'TokenReview',
status: {
authenticated: false,
user: {},
},
};
try {
let tokenData = this.extractAndVerifyToken(token);
let ldapObject = await this.authenticator.getAttributes(
tokenData.username,
this.mapping.getLdapAttributes()
);
responseData.status.user = this.mapping.ldapToKubernetes(ldapObject);
responseData.status.authenticated = true;
} catch (error) {
delete responseData.status.user;
responseData.status.authenticated = false;
this.logger.info('Error while verifying token: ' +
`[${error.name}] with message [${error.message}]`);
}
return responseData;
}

/**
* Validate token
* @param {string} token - The token.
Expand Down
Loading

0 comments on commit 7809f1f

Please sign in to comment.