Skip to content

Commit

Permalink
Certificate option name consistency
Browse files Browse the repository at this point in the history
  • Loading branch information
gimmyxd committed Jul 3, 2024
1 parent cf7c409 commit 11b9648
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 100 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type AuthorizerConfig = {
tenantId?: string;
authorizerApiKey?: string;
token?: string;
authorizerCertFile?: string;
caFile?: string;
insecure?: boolean;
};
```
Expand All @@ -63,14 +63,14 @@ const authClient = new Authorizer({
- `authorizerServiceUrl`: hostname:port of authorizer service (_required_)
- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)
- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)
- `authorizerCertFile`: Path to the authorizer CA file. (optional)
- `caFile`: Path to the authorizer CA file. (optional)
- `insecure`: Skip server certificate and domain verification. (NOT SECURE!). Defaults to `false`.

### Topaz
```ts
const authClient = new Authorizer({
authorizerServiceUrl: "localhost:8282",
authorizerCertFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`
caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`
});
```

Expand All @@ -86,7 +86,7 @@ import {
const authClient = new Authorizer(
{
authorizerServiceUrl: "localhost:8282",
authorizerCertFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`
caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`
},
);

Expand Down Expand Up @@ -820,7 +820,7 @@ By default, `jwtAuthz` derives the policy file name and resource key from the Ex
- `instanceLabel`: instance label (_required_ if using hosted authorizer)
- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)
- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)
- `authorizerCertFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.
- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.
- `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.
- `failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly.
- `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`.
Expand Down Expand Up @@ -869,7 +869,7 @@ app.use(displayStateMap(options));
- `instanceLabel`: instance label (_required_ if using hosted authorizer)
- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)
- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)
- `authorizerCertFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.
- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.
- `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.
- `endpointPath`: display state map endpoint path, defaults to `/__displaystatemap`.
- `failWithError`: When set to `true`, will forward errors to `next` instead of ending the response directly. Defaults to `false`.
Expand Down Expand Up @@ -932,7 +932,7 @@ The Express request object.
- `instanceLabel`: instance label (_required_ if using hosted authorizer)
- `authorizerApiKey`: API key for authorizer service (_required_ if using hosted authorizer)
- `tenantId`: Aserto tenant ID (_required_ if using hosted authorizer)
- `authorizerCertFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.
- `caFile`: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.
- `disableTlsValidation`: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.
- `useAuthorizationHeader`: When set to `true`, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to `true`.
- `identityHeader`: the name of the header from which to extract the `identity` field to pass into the `authorize` call. This only happens if `useAuthorizationHeader` is false. Defaults to 'identity'.
Expand Down Expand Up @@ -963,9 +963,9 @@ For a hosted authorizer that has a TLS certificate that is signed by a trusted C
In a development environment, [topaz](github.com/aserto-dev/topaz) automatically creates a set of self-signed certificates and certificates of the CA (certificate authority) that signed them. It places them in a well-known location on the filesystem, defaulting to `$HOME/.local/share/topaz/certs/` (or `$HOMEPATH\AppData\Local\topaz\certs\` on Windows).
In order for the `aserto-node` package to perform the TLS handshake, it needs to verify the TLS certificate of Topaz using the certificate of the CA that signed it - which was placed in `$HOME/.local/share/topaz/certs/grpc-ca.crt`. Therefore, in order for this middleware to work successfully, either the `authorizerCertFile` must be set to the correct path for the CA cert file, or the `disableTlsValidation` flag must be set to `true`. The same is true for the `caFile` argument of the `DirectoryClient`.
In order for the `aserto-node` package to perform the TLS handshake, it needs to verify the TLS certificate of Topaz using the certificate of the CA that signed it - which was placed in `$HOME/.local/share/topaz/certs/grpc-ca.crt`. Therefore, in order for this middleware to work successfully, either the `caFile` must be set to the correct path for the CA cert file, or the `disableTlsValidation` flag must be set to `true`. The same is true for the `caFile` argument of the `DirectoryClient`.
Furthermore, when packaging a policy for deployment (e.g. in a Docker container) which uses `aserto-node` to communicate with an authorizer that has a self-signed TLS certificate, you must copy this CA certificate into the container as part of the Docker build (typically performed in the Dockerfile). When you do that, you'll need to override the `authorizerCertFile` option that is passed into any of the API calls defined above with the location of this cert file.
Furthermore, when packaging a policy for deployment (e.g. in a Docker container) which uses `aserto-node` to communicate with an authorizer that has a self-signed TLS certificate, you must copy this CA certificate into the container as part of the Docker build (typically performed in the Dockerfile). When you do that, you'll need to override the `caFile` option that is passed into any of the API calls defined above with the location of this cert file.
Alternately, to ignore TLS certificate validation when creating a TLS connection to the authorizer, you can set the `disableTlsValidation` option to `true` and avoid TLS certificate validation. This option is **not recommended for production**.
Expand Down
111 changes: 110 additions & 1 deletion __tests__/integration/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import express, { Express } from "express";
import nJwt from "njwt";
import request from "supertest";

import {
AnonymousIdentityMapper,
Authorizer,
createAsyncIterable,
DirectoryServiceV3,
DirectoryV3,
displayStateMap,
EtagMismatchError,
ImportMsgCase,
ImportOpCode,
Expand Down Expand Up @@ -502,7 +507,7 @@ types:
beforeEach(async () => {
authorizerClient = new Authorizer({
authorizerServiceUrl: "localhost:8282",
authorizerCertFile: await topaz.caCert(),
caFile: await topaz.caCert(),
});
});

Expand Down Expand Up @@ -530,4 +535,108 @@ types:
});
});
});

describe("DisplayStateMap", () => {
const app: Express = express();
const jwt = nJwt.create({ sub: "[email protected]" }, "signingKey");

it("returns the correct data", async () => {
const options = {
policyRoot: "todoApp",
instanceLabel: "todo",
instanceName: "todo",
authorizerServiceUrl: "localhost:8282",
caFile: await topaz.caCert(),
failWithError: true,
};
app.use(
displayStateMap(
options,
undefined,
() => {
return AnonymousIdentityMapper();
},
() => {
return new Promise((resolve) => {
resolve(policyContext("todoApp", ["allowed"]));
});
}
)
);

const response = await request(app)
.get("/__displaystatemap")
.set("Content-type", "application/json")
.set("Authorization", `Bearer ${jwt}`);
expect(response.body).toEqual({
"todoApp/DELETE/todos/__id": {
allowed: false,
},
"todoApp/GET/todos": {
allowed: true,
},
"todoApp/GET/users/__userID": {
allowed: true,
},
"todoApp/POST/todos": {
allowed: false,
},
"todoApp/PUT/todos/__id": {
allowed: false,
},
});
});
});

describe("DisplayStateMap Legacy `authorizerCertCAFile`", () => {
const app: Express = express();
const jwt = nJwt.create({ sub: "[email protected]" }, "signingKey");

it("returns the correct data", async () => {
const options = {
policyRoot: "todoApp",
instanceLabel: "todo",
instanceName: "todo",
authorizerServiceUrl: "localhost:8282",
authorizerCertCAFile: await topaz.caCert(),
failWithError: true,
};
app.use(
displayStateMap(
options,
undefined,
() => {
return AnonymousIdentityMapper();
},
() => {
return new Promise((resolve) => {
resolve(policyContext("todoApp", ["allowed"]));
});
}
)
);

const response = await request(app)
.get("/__displaystatemap")
.set("Content-type", "application/json")
.set("Authorization", `Bearer ${jwt}`);
expect(response.body).toEqual({
"todoApp/DELETE/todos/__id": {
allowed: false,
},
"todoApp/GET/todos": {
allowed: true,
},
"todoApp/GET/users/__userID": {
allowed: true,
},
"todoApp/POST/todos": {
allowed: false,
},
"todoApp/PUT/todos/__id": {
allowed: false,
},
});
});
});
});
4 changes: 2 additions & 2 deletions __tests__/is.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe("should succeed", () => {
policyId: "123",
authorizerServiceUrl: "localhost:8282",
identityHeader: "Authorization",
authorizerCertCAFile: process.env.CA_FILE!,
caFile: process.env.CA_FILE!,
};

const allowed = await is(decision, req, options, packageName, resourceMap);
Expand All @@ -66,7 +66,7 @@ describe("should succeed", () => {
policyId: "123",
authorizerServiceUrl: "localhost:8282",
identityHeader: "Authorization",
authorizerCertCAFile: process.env.CA_FILE!,
caFike: process.env.CA_FILE!,
};

const allowed = await is(decision, req, options);
Expand Down
53 changes: 0 additions & 53 deletions __tests__/jwtAuthz.test.ts

This file was deleted.

10 changes: 4 additions & 6 deletions __tests__/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ describe("Middleware", () => {
const response = middleware.Check(options);

await response(request, res, next);
expect(next).toBeCalled();

expect(next).toHaveBeenCalled();
});
Expand Down Expand Up @@ -97,7 +96,7 @@ describe("Middleware", () => {
const response = middleware.Check(options);

await response(request, res, next);
expect(next).toBeCalledWith({
expect(next).toHaveBeenCalledWith({
statusCode: 403,
error: "Forbidden",
message: `aserto-node: Forbidden by policy examplePolicy.check`,
Expand Down Expand Up @@ -148,7 +147,7 @@ describe("Middleware", () => {
const response = middleware.Check(options);

await response(request, res, next);
expect(next).toBeCalledWith({
expect(next).toHaveBeenCalledWith({
statusCode: 403,
error: "Forbidden",
message: `aserto-node: Authorizer service unavailable`,
Expand Down Expand Up @@ -188,7 +187,6 @@ describe("Middleware", () => {
const response = middleware.Authz();

await response(request, res, next);
expect(next).toBeCalled();

expect(next).toHaveBeenCalled();
});
Expand Down Expand Up @@ -228,7 +226,7 @@ describe("Middleware", () => {
const response = middleware.Authz();

await response(request, res, next);
expect(next).toBeCalledWith({
expect(next).toHaveBeenCalledWith({
statusCode: 403,
error: "Forbidden",
message: `aserto-node: Forbidden by policy examplePolicy.GET.todos`,
Expand Down Expand Up @@ -272,7 +270,7 @@ describe("Middleware", () => {
const response = middleware.Authz();

await response(request, res, next);
expect(next).toBeCalledWith({
expect(next).toHaveBeenCalledWith({
statusCode: 403,
error: "Forbidden",
message: `aserto-node: Authorizer service unavailable`,
Expand Down
6 changes: 3 additions & 3 deletions lib/authorizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type AuthorizerConfig = {
authorizerApiKey?: string;
token?: string;
authorizerCertFile?: string;
caFile?: string;
insecure?: boolean;
};

Expand All @@ -50,9 +51,8 @@ export class Authorizer {

const baseServiceUrl =
config.authorizerServiceUrl || "authorizer.prod.aserto.com:8443";
const baseCaFile = !!config.authorizerCertFile
? readFileSync(config.authorizerCertFile)
: undefined;
const caFilePath = config.authorizerCertFile || config.caFile;
const baseCaFile = !!caFilePath ? readFileSync(caFilePath) : undefined;

const insecure = config?.insecure || false;
const baseNodeOptions = {
Expand Down
20 changes: 4 additions & 16 deletions lib/displayStateMap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextFunction, Request, Response } from "express";
import { IdentityContext } from "@aserto/node-authorizer/src/gen/cjs/aserto/authorizer/v2/api/identity_context_pb";

import { AuthzOptions } from ".";
import { Authorizer } from "./authorizer";
import BodyResourceMapper from "./authorizer/mapper/resource/body";
import {
Expand All @@ -15,22 +16,9 @@ import { errorHandler } from "./errorHandler";
import identityContext from "./identityContext";
import processOptions from "./processOptions";

interface DisplayStateMapOptions {
policyRoot: string;
instanceName: string;
instanceLabel?: string;
authorizerServiceUrl: string;
authorizerApiKey?: string;
tenantId?: string;
authorizerCertCAFile?: string;
disableTlsValidation?: boolean;
useAuthorizationHeader?: boolean;
identityHeader?: string;
failWithError?: boolean;
customUserKey?: string;
customSubjectKey?: string;
type DisplayStateMapOptions = AuthzOptions & {
endpointPath?: string;
}
};
const displayStateMap = (
optionsParam: DisplayStateMapOptions,
resourceMapper?: ResourceMapper,
Expand Down Expand Up @@ -81,7 +69,7 @@ const displayStateMap = (
authorizerServiceUrl: authorizerUrl,
tenantId: tenantId!,
authorizerApiKey: authorizerApiKey!,
authorizerCertFile: authorizerCertCAFile,
caFile: authorizerCertCAFile,
insecure: disableTlsValidation,
});

Expand Down
Loading

0 comments on commit 11b9648

Please sign in to comment.