Skip to content

Commit deb2026

Browse files
fix(rbac): handle postgres ssl connection for rbac backend plugin (#923)
Signed-off-by: Oleksandr Andriienko <[email protected]>
1 parent 435592b commit deb2026

File tree

2 files changed

+172
-30
lines changed

2 files changed

+172
-30
lines changed

plugins/rbac-backend/src/database/casbin-adapter-factory.test.ts

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { DatabaseService } from '@backstage/backend-plugin-api';
12
import { ConfigReader } from '@backstage/config';
23

34
import * as Knex from 'knex';
@@ -44,38 +45,146 @@ describe('CasbinAdapterFactory', () => {
4445
expect(newAdapterMock).toHaveBeenCalled();
4546
});
4647

47-
it('test building an adapter using a PostgreSQL configuration.', async () => {
48-
const db = Knex.knex({ client: MockClient });
49-
db.client = {
50-
config: {
51-
connection: {
52-
database: 'test-database',
53-
},
54-
},
55-
};
56-
const mockDatabaseManager = {
57-
getClient: jest.fn().mockImplementation(async (): Promise<Knex.Knex> => {
58-
return db;
59-
}),
60-
};
61-
process.env.TEST = 'test';
62-
const config = new ConfigReader({
63-
backend: {
64-
database: {
65-
client: 'pg',
48+
describe('build adapter with postgres configuration', () => {
49+
let mockDatabaseManager: DatabaseService;
50+
51+
beforeEach(() => {
52+
const db = Knex.knex({ client: MockClient });
53+
db.client = {
54+
config: {
6655
connection: {
67-
host: 'localhost',
68-
port: '5432',
69-
user: 'postgresUser',
70-
password: process.env.TEST,
56+
database: 'test-database',
7157
},
7258
},
73-
},
59+
};
60+
mockDatabaseManager = {
61+
getClient: jest
62+
.fn()
63+
.mockImplementation(async (): Promise<Knex.Knex> => {
64+
return db;
65+
}),
66+
};
67+
process.env.TEST = 'test';
68+
});
69+
70+
it('test building an adapter using a PostgreSQL configuration.', async () => {
71+
const config = new ConfigReader({
72+
backend: {
73+
database: {
74+
client: 'pg',
75+
connection: {
76+
host: 'localhost',
77+
port: '5432',
78+
user: 'postgresUser',
79+
password: process.env.TEST,
80+
},
81+
},
82+
},
83+
});
84+
const factory = new CasbinDBAdapterFactory(config, mockDatabaseManager);
85+
const adapter = await factory.createAdapter();
86+
expect(adapter).not.toBeNull();
87+
expect(newAdapterMock).toHaveBeenCalledWith({
88+
type: 'postgres',
89+
host: 'localhost',
90+
port: 5432,
91+
username: 'postgresUser',
92+
password: process.env.TEST,
93+
database: 'test-database',
94+
ssl: undefined,
95+
});
96+
});
97+
98+
it('test building an adapter using a PostgreSQL configuration with enabled ssl.', async () => {
99+
const config = new ConfigReader({
100+
backend: {
101+
database: {
102+
client: 'pg',
103+
connection: {
104+
host: 'localhost',
105+
port: '5432',
106+
user: 'postgresUser',
107+
password: process.env.TEST,
108+
ssl: true,
109+
},
110+
},
111+
},
112+
});
113+
const factory = new CasbinDBAdapterFactory(config, mockDatabaseManager);
114+
const adapter = await factory.createAdapter();
115+
expect(adapter).not.toBeNull();
116+
expect(newAdapterMock).toHaveBeenCalledWith({
117+
type: 'postgres',
118+
host: 'localhost',
119+
port: 5432,
120+
username: 'postgresUser',
121+
password: process.env.TEST,
122+
database: 'test-database',
123+
ssl: true,
124+
});
125+
});
126+
127+
it('test building an adapter using a PostgreSQL configuration with intentionally disabled ssl.', async () => {
128+
const config = new ConfigReader({
129+
backend: {
130+
database: {
131+
client: 'pg',
132+
connection: {
133+
host: 'localhost',
134+
port: '5432',
135+
user: 'postgresUser',
136+
password: process.env.TEST,
137+
ssl: false,
138+
},
139+
},
140+
},
141+
});
142+
const factory = new CasbinDBAdapterFactory(config, mockDatabaseManager);
143+
const adapter = await factory.createAdapter();
144+
expect(adapter).not.toBeNull();
145+
expect(newAdapterMock).toHaveBeenCalledWith({
146+
type: 'postgres',
147+
host: 'localhost',
148+
port: 5432,
149+
username: 'postgresUser',
150+
password: process.env.TEST,
151+
database: 'test-database',
152+
ssl: false,
153+
});
154+
});
155+
156+
it('test building an adapter using a PostgreSQL configuration with intentionally ssl and ca cert.', async () => {
157+
const config = new ConfigReader({
158+
backend: {
159+
database: {
160+
client: 'pg',
161+
connection: {
162+
host: 'localhost',
163+
port: '5432',
164+
user: 'postgresUser',
165+
password: process.env.TEST,
166+
ssl: {
167+
ca: 'abc',
168+
},
169+
},
170+
},
171+
},
172+
});
173+
const factory = new CasbinDBAdapterFactory(config, mockDatabaseManager);
174+
const adapter = await factory.createAdapter();
175+
expect(adapter).not.toBeNull();
176+
expect(newAdapterMock).toHaveBeenCalledWith({
177+
type: 'postgres',
178+
host: 'localhost',
179+
port: 5432,
180+
username: 'postgresUser',
181+
password: process.env.TEST,
182+
database: 'test-database',
183+
ssl: {
184+
ca: 'abc',
185+
},
186+
});
74187
});
75-
const factory = new CasbinDBAdapterFactory(config, mockDatabaseManager);
76-
const adapter = await factory.createAdapter();
77-
expect(adapter).not.toBeNull();
78-
expect(newAdapterMock).toHaveBeenCalled();
79188
});
80189

81190
it('ensure that building an adapter with an unknown configuration fails.', async () => {

plugins/rbac-backend/src/database/casbin-adapter-factory.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { PluginDatabaseManager } from '@backstage/backend-common';
2+
import { Config } from '@backstage/config';
23
import { ConfigApi } from '@backstage/core-plugin-api';
34

45
import TypeORMAdapter from 'typeorm-adapter';
56

67
import { resolve } from 'path';
8+
import { TlsOptions } from 'tls';
79

810
const DEFAULT_SQLITE3_STORAGE_FILE_NAME = 'rbac.sqlite';
911

@@ -20,14 +22,18 @@ export class CasbinDBAdapterFactory {
2022
let adapter;
2123
if (client === 'pg') {
2224
const knexClient = await this.databaseManager.getClient();
23-
const database = await knexClient.client.config.connection.database;
25+
const dbName = await knexClient.client.config.connection.database;
26+
27+
const ssl = this.handleSSL(databaseConfig!);
28+
2429
adapter = await TypeORMAdapter.newAdapter({
2530
type: 'postgres',
2631
host: databaseConfig?.getString('connection.host'),
2732
port: databaseConfig?.getNumber('connection.port'),
2833
username: databaseConfig?.getString('connection.user'),
2934
password: databaseConfig?.getString('connection.password'),
30-
database,
35+
ssl,
36+
database: dbName,
3137
});
3238
}
3339

@@ -53,4 +59,31 @@ export class CasbinDBAdapterFactory {
5359

5460
return adapter;
5561
}
62+
63+
private handleSSL(dbConfig: Config): boolean | TlsOptions | undefined {
64+
const connection = dbConfig.getOptional('connection');
65+
if (!connection) {
66+
return undefined;
67+
}
68+
const ssl = (connection as { ssl: Object | boolean | undefined }).ssl;
69+
70+
if (ssl === undefined) {
71+
return undefined;
72+
}
73+
74+
if (typeof ssl.valueOf() === 'boolean') {
75+
return ssl;
76+
}
77+
78+
if (typeof ssl.valueOf() === 'object') {
79+
const ca = (ssl as { ca: string }).ca;
80+
if (ca) {
81+
return { ca };
82+
}
83+
// SSL object was defined with some options that we don't support yet.
84+
return true;
85+
}
86+
87+
return undefined;
88+
}
5689
}

0 commit comments

Comments
 (0)