@kustodian/plugin-authelia 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,306 @@
1
+ # @kustodian/plugin-authelia
2
+
3
+ Authelia authentication provider plugin for Kustodian. This plugin enables integration with Authelia for authentication and authorization in your Kubernetes applications.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **OIDC Client Generation** - Automatically generate Authelia OIDC client configurations
8
+ - 🛡️ **Access Control Rules** - Define access control rules for your applications
9
+ - 🔑 **Secret Management** - Generate and hash secrets for OIDC clients
10
+ - ✅ **Configuration Validation** - Validate Authelia configurations using the CLI
11
+ - 📝 **YAML Export** - Export configurations as Authelia-compatible YAML
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pnpm add @kustodian/plugin-authelia
17
+ ```
18
+
19
+ ## Prerequisites
20
+
21
+ For full functionality, install the Authelia CLI:
22
+
23
+ ```bash
24
+ # macOS
25
+ brew install authelia
26
+
27
+ # Linux
28
+ # See: https://www.authelia.com/integration/deployment/installation/
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Basic Plugin Setup
34
+
35
+ ```typescript
36
+ import { create_authelia_plugin } from '@kustodian/plugin-authelia';
37
+
38
+ const authelia_plugin = create_authelia_plugin({
39
+ domain: 'auth.example.com',
40
+ default_policy: 'two_factor',
41
+ auto_generate_secrets: true,
42
+ output_dir: './authelia-config',
43
+ });
44
+ ```
45
+
46
+ ### CLI Commands
47
+
48
+ The plugin provides several CLI commands:
49
+
50
+ ```bash
51
+ # Check Authelia CLI availability
52
+ kustodian authelia check
53
+
54
+ # Generate a hashed password
55
+ kustodian authelia hash-password <password> [algorithm]
56
+
57
+ # Generate a random secret
58
+ kustodian authelia generate-secret [length]
59
+
60
+ # Generate OIDC client configuration
61
+ kustodian authelia generate-client <app-name> <redirect-uri>
62
+
63
+ # Validate Authelia configuration file
64
+ kustodian authelia validate-config <config-path>
65
+ ```
66
+
67
+ ### Programmatic Usage
68
+
69
+ #### Generate OIDC Client Configuration
70
+
71
+ ```typescript
72
+ import { generate_oidc_client } from '@kustodian/plugin-authelia';
73
+
74
+ const auth_config = {
75
+ provider: 'oidc' as const,
76
+ app_name: 'grafana',
77
+ app_display_name: 'Grafana',
78
+ external_host: 'https://grafana.example.com',
79
+ oidc: {
80
+ client_id: 'grafana',
81
+ redirect_uris: ['https://grafana.example.com/login/generic_oauth'],
82
+ scopes: ['openid', 'profile', 'email', 'groups'],
83
+ },
84
+ };
85
+
86
+ const options = {
87
+ domain: 'auth.example.com',
88
+ default_policy: 'two_factor' as const,
89
+ hash_algorithm: 'pbkdf2' as const,
90
+ auto_generate_secrets: true,
91
+ output_dir: './authelia-config',
92
+ };
93
+
94
+ const result = generate_oidc_client(auth_config, options);
95
+
96
+ if (result.success) {
97
+ console.log('Generated client:', result.value);
98
+ }
99
+ ```
100
+
101
+ #### Generate Access Control Rules
102
+
103
+ ```typescript
104
+ import { generate_access_control_rules } from '@kustodian/plugin-authelia';
105
+
106
+ const auth_config = {
107
+ provider: 'proxy' as const,
108
+ app_name: 'my-app',
109
+ external_host: 'https://app.example.com',
110
+ proxy: {
111
+ external_host: 'https://app.example.com',
112
+ internal_host: 'http://app.svc.cluster.local:8080',
113
+ policy: 'two_factor' as const,
114
+ skip_path_regex: '^/api/health.*',
115
+ },
116
+ };
117
+
118
+ const result = generate_access_control_rules(auth_config, options);
119
+
120
+ if (result.success) {
121
+ console.log('Generated rules:', result.value);
122
+ }
123
+ ```
124
+
125
+ #### Generate Complete Authelia Configuration
126
+
127
+ ```typescript
128
+ import {
129
+ generate_authelia_config,
130
+ config_to_yaml,
131
+ } from '@kustodian/plugin-authelia';
132
+
133
+ const auth_configs = [
134
+ {
135
+ provider: 'oidc' as const,
136
+ app_name: 'app1',
137
+ external_host: 'https://app1.example.com',
138
+ oidc: {
139
+ client_id: 'app1',
140
+ redirect_uris: ['https://app1.example.com/callback'],
141
+ },
142
+ },
143
+ {
144
+ provider: 'oidc' as const,
145
+ app_name: 'app2',
146
+ external_host: 'https://app2.example.com',
147
+ oidc: {
148
+ client_id: 'app2',
149
+ redirect_uris: ['https://app2.example.com/callback'],
150
+ },
151
+ },
152
+ ];
153
+
154
+ const config_result = generate_authelia_config(auth_configs, options);
155
+
156
+ if (config_result.success) {
157
+ const yaml_result = config_to_yaml(config_result.value);
158
+ if (yaml_result.success) {
159
+ console.log(yaml_result.value);
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Configuration
165
+
166
+ ### Plugin Options
167
+
168
+ | Option | Type | Default | Description |
169
+ | ----------------------- | --------------------------- | --------------------- | ---------------------------------------------- |
170
+ | `domain` | `string` | `undefined` | Authelia domain (e.g., auth.example.com) |
171
+ | `default_policy` | `AutheliaPolicyType` | `'two_factor'` | Default authorization policy |
172
+ | `hash_algorithm` | `'pbkdf2' \| 'argon2'` | `'pbkdf2'` | Secret hashing algorithm |
173
+ | `auto_generate_secrets` | `boolean` | `true` | Auto-generate client secrets |
174
+ | `output_dir` | `string` | `'./authelia-config'` | Output directory for generated configurations |
175
+
176
+ ### Auth Provider Types
177
+
178
+ - **`oidc`** - OpenID Connect provider
179
+ - **`proxy`** - Forward authentication/proxy mode
180
+ - **`header`** - Header-based authentication
181
+
182
+ ### Authorization Policies
183
+
184
+ - **`bypass`** - No authentication required
185
+ - **`one_factor`** - Single-factor authentication (username + password)
186
+ - **`two_factor`** - Two-factor authentication (MFA required)
187
+ - **`deny`** - Deny all access
188
+
189
+ ## Examples
190
+
191
+ ### Example 1: Grafana with OIDC
192
+
193
+ ```typescript
194
+ const grafana_config = {
195
+ provider: 'oidc' as const,
196
+ app_name: 'grafana',
197
+ app_display_name: 'Grafana',
198
+ app_description: 'Monitoring and observability platform',
199
+ app_launch_url: 'https://grafana.example.com',
200
+ external_host: 'https://grafana.example.com',
201
+ oidc: {
202
+ client_id: 'grafana',
203
+ redirect_uris: ['https://grafana.example.com/login/generic_oauth'],
204
+ scopes: ['openid', 'profile', 'email', 'groups'],
205
+ authorization_policy: 'two_factor' as const,
206
+ },
207
+ };
208
+ ```
209
+
210
+ ### Example 2: Forward Auth for Internal App
211
+
212
+ ```typescript
213
+ const internal_app_config = {
214
+ provider: 'proxy' as const,
215
+ app_name: 'internal-tool',
216
+ app_display_name: 'Internal Tool',
217
+ external_host: 'https://internal.example.com',
218
+ proxy: {
219
+ external_host: 'https://internal.example.com',
220
+ internal_host: 'http://internal-tool.default.svc.cluster.local:80',
221
+ policy: 'two_factor' as const,
222
+ skip_path_regex: '^/(health|metrics)$',
223
+ networks: ['10.0.0.0/8'], // Only allow from internal network
224
+ },
225
+ };
226
+ ```
227
+
228
+ ### Example 3: Public App with Bypass
229
+
230
+ ```typescript
231
+ const public_app_config = {
232
+ provider: 'proxy' as const,
233
+ app_name: 'public-site',
234
+ external_host: 'https://public.example.com',
235
+ proxy: {
236
+ external_host: 'https://public.example.com',
237
+ internal_host: 'http://public-site.default.svc.cluster.local:80',
238
+ policy: 'bypass' as const, // No authentication required
239
+ },
240
+ };
241
+ ```
242
+
243
+ ## Integration with Kustodian
244
+
245
+ The plugin integrates with Kustodian's template system through hooks:
246
+
247
+ 1. **`generator:after_resolve`** - Extracts auth configurations from templates
248
+ 2. **`generator:before`** - Injects generated Authelia configurations
249
+
250
+ ### Template Integration (Future)
251
+
252
+ ```yaml
253
+ # Future feature - not yet implemented
254
+ apiVersion: kustodian.io/v1
255
+ kind: Template
256
+ metadata:
257
+ name: grafana
258
+ spec:
259
+ kustomizations:
260
+ - name: grafana
261
+ path: grafana
262
+ auth:
263
+ provider: oidc
264
+ app_name: grafana
265
+ app_display_name: Grafana
266
+ oidc:
267
+ redirect_uris:
268
+ - https://grafana.${cluster_domain}/login/generic_oauth
269
+ ```
270
+
271
+ ## Development
272
+
273
+ ### Running Tests
274
+
275
+ ```bash
276
+ cd plugins/authelia
277
+ bun test
278
+ ```
279
+
280
+ ### Type Checking
281
+
282
+ ```bash
283
+ pnpm run typecheck
284
+ ```
285
+
286
+ ## API Reference
287
+
288
+ See the [TypeScript types](./src/types.ts) for complete API documentation.
289
+
290
+ ## Related Documentation
291
+
292
+ - [Authelia Documentation](https://www.authelia.com/)
293
+ - [Authelia Access Control](https://www.authelia.com/configuration/security/access-control/)
294
+ - [Authelia OIDC](https://www.authelia.com/configuration/identity-providers/openid-connect/)
295
+
296
+ ## License
297
+
298
+ MIT
299
+
300
+ ## Sources
301
+
302
+ This plugin was implemented using the official Authelia documentation:
303
+
304
+ - [Access Control Configuration](https://www.authelia.com/configuration/security/access-control/)
305
+ - [OIDC Clients Configuration](https://www.authelia.com/configuration/identity-providers/openid-connect/clients.md)
306
+ - [Authelia GitHub Repository](https://github.com/authelia/authelia)
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kustodian/plugin-authelia",
3
+ "version": "1.0.0",
4
+ "description": "Authelia authentication provider plugin for Kustodian",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "import": "./src/index.ts"
12
+ }
13
+ },
14
+ "files": ["src"],
15
+ "scripts": {
16
+ "test": "bun test",
17
+ "test:watch": "bun test --watch",
18
+ "typecheck": "bun run tsc --noEmit"
19
+ },
20
+ "keywords": ["kustodian", "plugin", "authelia", "authentication", "oidc", "kubernetes"],
21
+ "author": "Luca Silverentand <luca@onezero.company>",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/lucasilverentand/kustodian.git",
26
+ "directory": "plugins/authelia"
27
+ },
28
+ "publishConfig": {
29
+ "registry": "https://npm.pkg.github.com"
30
+ },
31
+ "dependencies": {
32
+ "@kustodian/core": "^1.1.0",
33
+ "@kustodian/plugins": "^1.0.1",
34
+ "@kustodian/schema": "^1.2.0",
35
+ "js-yaml": "^4.1.1",
36
+ "zod": "^4.3.5"
37
+ },
38
+ "packageManager": "pnpm@10.19.0",
39
+ "devDependencies": {
40
+ "@types/js-yaml": "^4.0.9"
41
+ }
42
+ }
@@ -0,0 +1,114 @@
1
+ import { exec } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { type KustodianErrorType, type ResultType, create_error, success } from '@kustodian/core';
4
+
5
+ const exec_async = promisify(exec);
6
+
7
+ /**
8
+ * Check if authelia CLI is available
9
+ */
10
+ export async function check_authelia_available(): Promise<ResultType<string, KustodianErrorType>> {
11
+ try {
12
+ const { stdout } = await exec_async('authelia --version', { timeout: 5000 });
13
+ const version = stdout.trim();
14
+ return success(version);
15
+ } catch (error) {
16
+ return {
17
+ success: false,
18
+ error: create_error(
19
+ 'AUTHELIA_CLI_NOT_FOUND',
20
+ 'Authelia CLI not found. Install from: https://www.authelia.com/integration/deployment/installation/',
21
+ error,
22
+ ),
23
+ };
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Generate a hashed password using Authelia CLI
29
+ * @param password - Plain text password to hash
30
+ * @param algorithm - Hashing algorithm (pbkdf2 or argon2)
31
+ */
32
+ export async function hash_password(
33
+ password: string,
34
+ algorithm: 'pbkdf2' | 'argon2' = 'pbkdf2',
35
+ ): Promise<ResultType<string, KustodianErrorType>> {
36
+ try {
37
+ const cmd =
38
+ algorithm === 'argon2'
39
+ ? `authelia crypto hash generate argon2 --password '${password}'`
40
+ : `authelia crypto hash generate pbkdf2 --password '${password}'`;
41
+
42
+ const { stdout } = await exec_async(cmd, { timeout: 10000 });
43
+
44
+ // Extract the hash from output (format: "Digest: $hash...")
45
+ const hash_match = stdout.match(/Digest: (.+)/);
46
+ if (!hash_match?.[1]) {
47
+ return {
48
+ success: false,
49
+ error: create_error(
50
+ 'AUTHELIA_HASH_GENERATION_FAILED',
51
+ 'Failed to extract hash from authelia output',
52
+ ),
53
+ };
54
+ }
55
+
56
+ return success(hash_match[1].trim());
57
+ } catch (error) {
58
+ return {
59
+ success: false,
60
+ error: create_error(
61
+ 'AUTHELIA_HASH_GENERATION_FAILED',
62
+ `Failed to hash password: ${error instanceof Error ? error.message : String(error)}`,
63
+ error,
64
+ ),
65
+ };
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Generate a random secret suitable for OIDC client secrets
71
+ */
72
+ export async function generate_random_secret(
73
+ length = 64,
74
+ ): Promise<ResultType<string, KustodianErrorType>> {
75
+ try {
76
+ const { stdout } = await exec_async(
77
+ `authelia crypto rand --length ${length} --charset alphanumeric`,
78
+ {
79
+ timeout: 5000,
80
+ },
81
+ );
82
+ return success(stdout.trim());
83
+ } catch (error) {
84
+ return {
85
+ success: false,
86
+ error: create_error(
87
+ 'AUTHELIA_SECRET_GENERATION_FAILED',
88
+ `Failed to generate random secret: ${error instanceof Error ? error.message : String(error)}`,
89
+ error,
90
+ ),
91
+ };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Validate access control configuration using Authelia CLI
97
+ */
98
+ export async function validate_access_control(
99
+ config_path: string,
100
+ ): Promise<ResultType<boolean, KustodianErrorType>> {
101
+ try {
102
+ await exec_async(`authelia validate-config ${config_path}`, { timeout: 10000 });
103
+ return success(true);
104
+ } catch (error) {
105
+ return {
106
+ success: false,
107
+ error: create_error(
108
+ 'AUTHELIA_CONFIG_VALIDATION_FAILED',
109
+ `Configuration validation failed: ${error instanceof Error ? error.message : String(error)}`,
110
+ error,
111
+ ),
112
+ };
113
+ }
114
+ }
@@ -0,0 +1,236 @@
1
+ import {
2
+ type KustodianErrorType,
3
+ type ResultType,
4
+ create_error,
5
+ failure,
6
+ success,
7
+ } from '@kustodian/core';
8
+ import * as yaml from 'js-yaml';
9
+ import type {
10
+ AccessControlRuleType,
11
+ AuthConfigType,
12
+ AutheliaConfigType,
13
+ AutheliaPluginOptionsType,
14
+ OIDCClientConfigType,
15
+ } from './types.js';
16
+
17
+ /**
18
+ * Generates an OIDC client configuration from auth config
19
+ */
20
+ export function generate_oidc_client(
21
+ auth_config: AuthConfigType,
22
+ options: AutheliaPluginOptionsType,
23
+ ): ResultType<OIDCClientConfigType, KustodianErrorType> {
24
+ try {
25
+ const client_id = auth_config.app_name;
26
+
27
+ // Build base client config
28
+ const client: OIDCClientConfigType = {
29
+ client_id,
30
+ client_name: auth_config.app_display_name ?? auth_config.app_name,
31
+ public: auth_config.oidc?.public ?? false,
32
+ authorization_policy: auth_config.oidc?.authorization_policy ?? options.default_policy,
33
+ require_pkce: auth_config.oidc?.require_pkce ?? true,
34
+ pkce_challenge_method: auth_config.oidc?.pkce_challenge_method ?? 'S256',
35
+ redirect_uris: auth_config.oidc?.redirect_uris ?? [],
36
+ scopes: auth_config.oidc?.scopes ?? ['openid', 'profile', 'email', 'groups'],
37
+ response_types: auth_config.oidc?.response_types ?? ['code'],
38
+ grant_types: auth_config.oidc?.grant_types ?? ['authorization_code'],
39
+ token_endpoint_auth_method:
40
+ auth_config.oidc?.token_endpoint_auth_method ?? 'client_secret_basic',
41
+ };
42
+
43
+ // Add client secret if not public and auto-generation is enabled
44
+ if (!client.public && options.auto_generate_secrets) {
45
+ // In production, this should generate a proper hashed secret
46
+ // For now, we'll add a placeholder that users must replace
47
+ client.client_secret = `\${${client_id.toUpperCase().replace(/-/g, '_')}_CLIENT_SECRET}`;
48
+ } else if (auth_config.oidc?.client_secret) {
49
+ client.client_secret = auth_config.oidc.client_secret;
50
+ }
51
+
52
+ // Add optional fields
53
+ if (auth_config.oidc?.consent_mode) {
54
+ client.consent_mode = auth_config.oidc.consent_mode;
55
+ }
56
+
57
+ if (auth_config.oidc?.pre_configured_consent_duration) {
58
+ client.pre_configured_consent_duration = auth_config.oidc.pre_configured_consent_duration;
59
+ }
60
+
61
+ if (auth_config.oidc?.audience) {
62
+ client.audience = auth_config.oidc.audience;
63
+ }
64
+
65
+ // Merge additional options
66
+ if (auth_config.oidc?.additional_options) {
67
+ Object.assign(client, auth_config.oidc.additional_options);
68
+ }
69
+
70
+ return success(client);
71
+ } catch (error) {
72
+ return failure(
73
+ create_error(
74
+ 'AUTHELIA_CLIENT_GENERATION_FAILED',
75
+ `Failed to generate OIDC client: ${error instanceof Error ? error.message : String(error)}`,
76
+ ),
77
+ );
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Generates access control rules from auth config
83
+ */
84
+ export function generate_access_control_rules(
85
+ auth_config: AuthConfigType,
86
+ _options: AutheliaPluginOptionsType,
87
+ ): ResultType<AccessControlRuleType[], KustodianErrorType> {
88
+ try {
89
+ const rules: AccessControlRuleType[] = [];
90
+
91
+ // Add custom access control rules if provided
92
+ if (auth_config.access_control) {
93
+ rules.push(...auth_config.access_control);
94
+ }
95
+
96
+ // Generate proxy/forward auth rules
97
+ if (auth_config.provider === 'proxy' && auth_config.external_host) {
98
+ const domain = new URL(auth_config.external_host).hostname;
99
+ const rule: AccessControlRuleType = {
100
+ domain,
101
+ policy: auth_config.proxy?.policy ?? 'two_factor',
102
+ };
103
+
104
+ // Add networks if specified
105
+ if (auth_config.proxy?.networks) {
106
+ rule.networks = auth_config.proxy.networks;
107
+ }
108
+
109
+ // Add subject if specified
110
+ if (auth_config.proxy?.subject) {
111
+ rule.subject = auth_config.proxy.subject;
112
+ }
113
+
114
+ // Add resource patterns if skip_path_regex is specified
115
+ if (auth_config.proxy?.skip_path_regex) {
116
+ // Create a bypass rule for skipped paths
117
+ rules.push({
118
+ domain,
119
+ policy: 'bypass',
120
+ resources: [auth_config.proxy.skip_path_regex],
121
+ });
122
+ }
123
+
124
+ rules.push(rule);
125
+ }
126
+
127
+ // Generate OIDC access control rules
128
+ if (auth_config.provider === 'oidc' && auth_config.external_host) {
129
+ const domain = new URL(auth_config.external_host).hostname;
130
+ rules.push({
131
+ domain,
132
+ policy: auth_config.oidc?.authorization_policy ?? 'two_factor',
133
+ });
134
+ }
135
+
136
+ return success(rules);
137
+ } catch (error) {
138
+ return failure(
139
+ create_error(
140
+ 'AUTHELIA_ACCESS_CONTROL_GENERATION_FAILED',
141
+ `Failed to generate access control rules: ${error instanceof Error ? error.message : String(error)}`,
142
+ ),
143
+ );
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Generates complete Authelia configuration from multiple auth configs
149
+ */
150
+ export function generate_authelia_config(
151
+ auth_configs: AuthConfigType[],
152
+ options: AutheliaPluginOptionsType,
153
+ ): ResultType<AutheliaConfigType, KustodianErrorType> {
154
+ try {
155
+ const config: AutheliaConfigType = {
156
+ identity_providers: {
157
+ oidc: {
158
+ clients: [],
159
+ },
160
+ },
161
+ access_control: {
162
+ default_policy: options.default_policy,
163
+ rules: [],
164
+ },
165
+ };
166
+
167
+ // Generate OIDC clients and access control rules
168
+ for (const auth_config of auth_configs) {
169
+ // Generate OIDC client if provider is OIDC
170
+ if (auth_config.provider === 'oidc') {
171
+ const client_result = generate_oidc_client(auth_config, options);
172
+ if (!client_result.success) {
173
+ return client_result;
174
+ }
175
+ config.identity_providers?.oidc?.clients?.push(client_result.value);
176
+ }
177
+
178
+ // Generate access control rules
179
+ const rules_result = generate_access_control_rules(auth_config, options);
180
+ if (!rules_result.success) {
181
+ return rules_result;
182
+ }
183
+ config.access_control?.rules?.push(...rules_result.value);
184
+ }
185
+
186
+ return success(config);
187
+ } catch (error) {
188
+ return failure(
189
+ create_error(
190
+ 'AUTHELIA_CONFIG_GENERATION_FAILED',
191
+ `Failed to generate Authelia configuration: ${error instanceof Error ? error.message : String(error)}`,
192
+ ),
193
+ );
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Converts Authelia configuration to YAML string
199
+ */
200
+ export function config_to_yaml(config: AutheliaConfigType): ResultType<string, KustodianErrorType> {
201
+ try {
202
+ const yaml_output = yaml.dump(config, {
203
+ indent: 2,
204
+ lineWidth: 120,
205
+ noRefs: true,
206
+ sortKeys: false,
207
+ });
208
+ return success(yaml_output);
209
+ } catch (error) {
210
+ return failure(
211
+ create_error(
212
+ 'AUTHELIA_YAML_SERIALIZATION_FAILED',
213
+ `Failed to convert config to YAML: ${error instanceof Error ? error.message : String(error)}`,
214
+ ),
215
+ );
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Parses YAML string to Authelia configuration
221
+ */
222
+ export function yaml_to_config(
223
+ yaml_string: string,
224
+ ): ResultType<AutheliaConfigType, KustodianErrorType> {
225
+ try {
226
+ const config = yaml.load(yaml_string) as AutheliaConfigType;
227
+ return success(config);
228
+ } catch (error) {
229
+ return failure(
230
+ create_error(
231
+ 'AUTHELIA_YAML_PARSING_FAILED',
232
+ `Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`,
233
+ ),
234
+ );
235
+ }
236
+ }
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Authelia authentication provider plugin for Kustodian
3
+ *
4
+ * This plugin enables integration with Authelia for authentication and authorization.
5
+ * It can generate OIDC client configurations, access control rules, and manage
6
+ * authentication requirements for deployed applications.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ export { create_authelia_plugin, plugin as default } from './plugin.js';
12
+ export type {
13
+ AuthConfigType,
14
+ AuthProviderType,
15
+ AutheliaPolicyType,
16
+ AutheliaPluginOptionsType,
17
+ OIDCClientConfigType,
18
+ AccessControlRuleType,
19
+ ProxyAuthConfigType,
20
+ ConsentModeType,
21
+ PKCEChallengeMethodType,
22
+ TokenEndpointAuthMethodType,
23
+ } from './types.js';
24
+ export {
25
+ generate_oidc_client,
26
+ generate_access_control_rules,
27
+ generate_authelia_config,
28
+ config_to_yaml,
29
+ yaml_to_config,
30
+ } from './generator.js';
31
+ export {
32
+ check_authelia_available,
33
+ hash_password,
34
+ generate_random_secret,
35
+ validate_access_control,
36
+ } from './executor.js';
package/src/plugin.ts ADDED
@@ -0,0 +1,249 @@
1
+ import { success } from '@kustodian/core';
2
+ import type {
3
+ CommandType,
4
+ HookContextType,
5
+ HookEventType,
6
+ KustodianPluginType,
7
+ PluginCommandContributionType,
8
+ PluginHookContributionType,
9
+ PluginManifestType,
10
+ } from '@kustodian/plugins';
11
+
12
+ import {
13
+ check_authelia_available,
14
+ generate_random_secret,
15
+ hash_password,
16
+ validate_access_control,
17
+ } from './executor.js';
18
+ import { generate_oidc_client } from './generator.js';
19
+ import type { AuthConfigType } from './types.js';
20
+ import { authelia_plugin_options_schema } from './types.js';
21
+
22
+ /**
23
+ * Authelia plugin manifest.
24
+ */
25
+ const manifest: PluginManifestType = {
26
+ name: '@kustodian/plugin-authelia',
27
+ version: '1.0.0',
28
+ description: 'Authelia authentication provider plugin for Kustodian',
29
+ capabilities: ['commands', 'hooks'],
30
+ };
31
+
32
+ /**
33
+ * Creates the Authelia plugin.
34
+ */
35
+ export function create_authelia_plugin(options: Record<string, unknown> = {}): KustodianPluginType {
36
+ // Parse options through schema to apply defaults
37
+ const plugin_options = authelia_plugin_options_schema.parse(options);
38
+
39
+ return {
40
+ manifest,
41
+
42
+ async activate() {
43
+ // Verify CLI availability on activation (warning only)
44
+ const check_result = await check_authelia_available();
45
+ if (!check_result.success) {
46
+ console.warn('Authelia CLI not found - some features may be unavailable');
47
+ console.warn('Install from: https://www.authelia.com/integration/deployment/installation/');
48
+ }
49
+ return success(undefined);
50
+ },
51
+
52
+ async deactivate() {
53
+ return success(undefined);
54
+ },
55
+
56
+ get_commands(): PluginCommandContributionType[] {
57
+ const authelia_command: CommandType = {
58
+ name: 'authelia',
59
+ description: 'Authelia authentication provider commands',
60
+ subcommands: [
61
+ {
62
+ name: 'check',
63
+ description: 'Check Authelia CLI availability',
64
+ handler: async () => {
65
+ const result = await check_authelia_available();
66
+ if (result.success) {
67
+ console.log(`Authelia CLI: ${result.value}`);
68
+ return success(undefined);
69
+ }
70
+ console.error('Authelia CLI not available');
71
+ return result;
72
+ },
73
+ },
74
+ {
75
+ name: 'hash-password',
76
+ description: 'Generate hashed password for Authelia',
77
+ arguments: [
78
+ {
79
+ name: 'password',
80
+ description: 'Password to hash',
81
+ required: true,
82
+ },
83
+ {
84
+ name: 'algorithm',
85
+ description: 'Hashing algorithm (pbkdf2 or argon2)',
86
+ required: false,
87
+ },
88
+ ],
89
+ handler: async (ctx) => {
90
+ const password = ctx.args[0];
91
+ const algorithm = (ctx.args[1] as 'pbkdf2' | 'argon2') ?? 'pbkdf2';
92
+
93
+ if (!password) {
94
+ console.error('Password is required');
95
+ return success(undefined);
96
+ }
97
+
98
+ const result = await hash_password(password, algorithm);
99
+ if (result.success) {
100
+ console.log('Hashed password:');
101
+ console.log(result.value);
102
+ return success(undefined);
103
+ }
104
+
105
+ console.error(`Failed to hash password: ${result.error.message}`);
106
+ return result;
107
+ },
108
+ },
109
+ {
110
+ name: 'generate-secret',
111
+ description: 'Generate random secret for OIDC client',
112
+ arguments: [
113
+ {
114
+ name: 'length',
115
+ description: 'Secret length (default: 64)',
116
+ required: false,
117
+ },
118
+ ],
119
+ handler: async (ctx) => {
120
+ const length = ctx.args[0] ? Number.parseInt(ctx.args[0], 10) : 64;
121
+
122
+ const result = await generate_random_secret(length);
123
+ if (result.success) {
124
+ console.log('Generated secret:');
125
+ console.log(result.value);
126
+ return success(undefined);
127
+ }
128
+
129
+ console.error(`Failed to generate secret: ${result.error.message}`);
130
+ return result;
131
+ },
132
+ },
133
+ {
134
+ name: 'generate-client',
135
+ description: 'Generate OIDC client configuration',
136
+ arguments: [
137
+ {
138
+ name: 'app-name',
139
+ description: 'Application name',
140
+ required: true,
141
+ },
142
+ {
143
+ name: 'redirect-uri',
144
+ description: 'OAuth redirect URI',
145
+ required: true,
146
+ },
147
+ ],
148
+ handler: async (ctx) => {
149
+ const app_name = ctx.args[0];
150
+ const redirect_uri = ctx.args[1];
151
+
152
+ if (!app_name || !redirect_uri) {
153
+ console.error('App name and redirect URI are required');
154
+ return success(undefined);
155
+ }
156
+
157
+ const auth_config: AuthConfigType = {
158
+ provider: 'oidc',
159
+ app_name,
160
+ oidc: {
161
+ client_id: app_name,
162
+ redirect_uris: [redirect_uri],
163
+ },
164
+ };
165
+
166
+ const result = generate_oidc_client(auth_config, plugin_options);
167
+ if (result.success) {
168
+ console.log('Generated OIDC client configuration:');
169
+ console.log(JSON.stringify(result.value, null, 2));
170
+ return success(undefined);
171
+ }
172
+
173
+ console.error(`Failed to generate client: ${result.error.message}`);
174
+ return result;
175
+ },
176
+ },
177
+ {
178
+ name: 'validate-config',
179
+ description: 'Validate Authelia configuration file',
180
+ arguments: [
181
+ {
182
+ name: 'config-path',
183
+ description: 'Path to Authelia configuration file',
184
+ required: true,
185
+ },
186
+ ],
187
+ handler: async (ctx) => {
188
+ const config_path = ctx.args[0];
189
+
190
+ if (!config_path) {
191
+ console.error('Configuration path is required');
192
+ return success(undefined);
193
+ }
194
+
195
+ const result = await validate_access_control(config_path);
196
+ if (result.success) {
197
+ console.log('✓ Configuration is valid');
198
+ return success(undefined);
199
+ }
200
+
201
+ console.error(`✗ Configuration validation failed: ${result.error.message}`);
202
+ return result;
203
+ },
204
+ },
205
+ ],
206
+ };
207
+ return [{ command: authelia_command }];
208
+ },
209
+
210
+ get_hooks(): PluginHookContributionType[] {
211
+ return [
212
+ {
213
+ event: 'generator:after_resolve',
214
+ priority: 40, // Run before secret providers to allow auth configs to be processed
215
+ handler: async (_event: HookEventType, ctx: HookContextType) => {
216
+ // TODO: Implement auth config extraction from templates
217
+ // This will:
218
+ // 1. Extract auth configs from kustomizations
219
+ // 2. Generate Authelia OIDC clients and access control rules
220
+ // 3. Write configuration to output directory
221
+ // 4. Generate Kubernetes secrets for client credentials
222
+
223
+ // For now, just pass through
224
+ return success(ctx);
225
+ },
226
+ },
227
+ {
228
+ event: 'generator:before',
229
+ priority: 50,
230
+ handler: async (_event: HookEventType, ctx: HookContextType) => {
231
+ // TODO: This hook could be used to:
232
+ // 1. Inject generated Authelia configurations as additional kustomizations
233
+ // 2. Add ConfigMaps with Authelia config fragments
234
+ // 3. Generate documentation for configured auth apps
235
+
236
+ return success(ctx);
237
+ },
238
+ },
239
+ ];
240
+ },
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Default plugin export.
246
+ */
247
+ export const plugin = create_authelia_plugin();
248
+
249
+ export default plugin;
package/src/types.ts ADDED
@@ -0,0 +1,181 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Authelia access control policy types
5
+ */
6
+ export const authelia_policy_schema = z.enum(['bypass', 'one_factor', 'two_factor', 'deny']);
7
+ export type AutheliaPolicyType = z.infer<typeof authelia_policy_schema>;
8
+
9
+ /**
10
+ * Authelia PKCE challenge method
11
+ */
12
+ export const pkce_challenge_method_schema = z.enum(['plain', 'S256']);
13
+ export type PKCEChallengeMethodType = z.infer<typeof pkce_challenge_method_schema>;
14
+
15
+ /**
16
+ * Authelia token endpoint auth method
17
+ */
18
+ export const token_endpoint_auth_method_schema = z.enum([
19
+ 'client_secret_basic',
20
+ 'client_secret_post',
21
+ 'client_secret_jwt',
22
+ 'private_key_jwt',
23
+ 'none',
24
+ ]);
25
+ export type TokenEndpointAuthMethodType = z.infer<typeof token_endpoint_auth_method_schema>;
26
+
27
+ /**
28
+ * Authelia consent mode
29
+ */
30
+ export const consent_mode_schema = z.enum(['explicit', 'implicit', 'pre-configured']);
31
+ export type ConsentModeType = z.infer<typeof consent_mode_schema>;
32
+
33
+ /**
34
+ * Authentication provider types supported by the plugin
35
+ */
36
+ export const auth_provider_schema = z.enum(['oidc', 'proxy', 'header']);
37
+ export type AuthProviderType = z.infer<typeof auth_provider_schema>;
38
+
39
+ /**
40
+ * OIDC client configuration for Authelia
41
+ */
42
+ export const oidc_client_config_schema = z.object({
43
+ /** Unique client identifier */
44
+ client_id: z.string(),
45
+ /** Display name for the client */
46
+ client_name: z.string().optional(),
47
+ /** Client secret (will be hashed) */
48
+ client_secret: z.string().optional(),
49
+ /** Whether this is a public client (no secret) */
50
+ public: z.boolean().default(false),
51
+ /** Authorization policy (default: two_factor) */
52
+ authorization_policy: authelia_policy_schema.default('two_factor'),
53
+ /** Require PKCE for authorization code flow */
54
+ require_pkce: z.boolean().default(true),
55
+ /** PKCE challenge method */
56
+ pkce_challenge_method: pkce_challenge_method_schema.default('S256'),
57
+ /** Redirect URIs for OAuth callbacks */
58
+ redirect_uris: z.array(z.string()),
59
+ /** Scopes to grant (default: openid, profile, email, groups) */
60
+ scopes: z.array(z.string()).default(['openid', 'profile', 'email', 'groups']),
61
+ /** Response types (default: code) */
62
+ response_types: z.array(z.string()).default(['code']),
63
+ /** Grant types (default: authorization_code) */
64
+ grant_types: z.array(z.string()).default(['authorization_code']),
65
+ /** Token endpoint authentication method */
66
+ token_endpoint_auth_method: token_endpoint_auth_method_schema.default('client_secret_basic'),
67
+ /** Consent mode */
68
+ consent_mode: consent_mode_schema.optional(),
69
+ /** Pre-configured consent duration (e.g., '1 week') */
70
+ pre_configured_consent_duration: z.string().optional(),
71
+ /** Audience for the client */
72
+ audience: z.array(z.string()).optional(),
73
+ /** Additional Authelia client options */
74
+ additional_options: z.record(z.string(), z.unknown()).optional(),
75
+ });
76
+ export type OIDCClientConfigType = z.infer<typeof oidc_client_config_schema>;
77
+
78
+ /**
79
+ * Access control rule configuration for Authelia
80
+ */
81
+ export const access_control_rule_schema = z.object({
82
+ /** Domain(s) to match */
83
+ domain: z.union([z.string(), z.array(z.string())]),
84
+ /** Domain regex pattern (alternative to domain) */
85
+ domain_regex: z.string().optional(),
86
+ /** Policy to apply */
87
+ policy: authelia_policy_schema,
88
+ /** Networks to match */
89
+ networks: z.array(z.string()).optional(),
90
+ /** Subject(s) to match (users/groups) */
91
+ subject: z.union([z.string(), z.array(z.string()), z.array(z.array(z.string()))]).optional(),
92
+ /** HTTP methods to match */
93
+ methods: z.array(z.string()).optional(),
94
+ /** Resource patterns to match */
95
+ resources: z.array(z.string()).optional(),
96
+ /** Query parameter conditions */
97
+ query: z.array(z.array(z.record(z.string(), z.unknown()))).optional(),
98
+ });
99
+ export type AccessControlRuleType = z.infer<typeof access_control_rule_schema>;
100
+
101
+ /**
102
+ * Proxy/Forward Auth configuration
103
+ */
104
+ export const proxy_auth_config_schema = z.object({
105
+ /** External host for the application */
106
+ external_host: z.string(),
107
+ /** Internal service host */
108
+ internal_host: z.string(),
109
+ /** Paths to skip authentication */
110
+ skip_path_regex: z.string().optional(),
111
+ /** Access control policy */
112
+ policy: authelia_policy_schema.default('two_factor'),
113
+ /** Allowed networks */
114
+ networks: z.array(z.string()).optional(),
115
+ /** Allowed subjects (users/groups) */
116
+ subject: z.union([z.string(), z.array(z.string())]).optional(),
117
+ });
118
+ export type ProxyAuthConfigType = z.infer<typeof proxy_auth_config_schema>;
119
+
120
+ /**
121
+ * Authentication configuration in template kustomizations
122
+ */
123
+ export const auth_config_schema = z.object({
124
+ /** Authentication provider type */
125
+ provider: auth_provider_schema,
126
+ /** Application name (used as client_id for OIDC) */
127
+ app_name: z.string(),
128
+ /** Display name for the application */
129
+ app_display_name: z.string().optional(),
130
+ /** Application description */
131
+ app_description: z.string().optional(),
132
+ /** Application icon URL */
133
+ app_icon: z.string().optional(),
134
+ /** Application group/category */
135
+ app_group: z.string().optional(),
136
+ /** Application launch URL */
137
+ app_launch_url: z.string().optional(),
138
+ /** External host (for proxy/access control) */
139
+ external_host: z.string().optional(),
140
+ /** Internal service host (for proxy) */
141
+ internal_host: z.string().optional(),
142
+ /** OIDC-specific configuration */
143
+ oidc: oidc_client_config_schema.partial().optional(),
144
+ /** Proxy-specific configuration */
145
+ proxy: proxy_auth_config_schema.partial().optional(),
146
+ /** Custom access control rules */
147
+ access_control: z.array(access_control_rule_schema).optional(),
148
+ });
149
+ export type AuthConfigType = z.infer<typeof auth_config_schema>;
150
+
151
+ /**
152
+ * Authelia plugin options
153
+ */
154
+ export const authelia_plugin_options_schema = z.object({
155
+ /** Authelia domain (e.g., auth.example.com) */
156
+ domain: z.string().optional(),
157
+ /** Default authorization policy */
158
+ default_policy: authelia_policy_schema.default('two_factor'),
159
+ /** Secret hashing algorithm (for client secrets) */
160
+ hash_algorithm: z.enum(['pbkdf2', 'argon2']).default('pbkdf2'),
161
+ /** Whether to generate client secrets automatically */
162
+ auto_generate_secrets: z.boolean().default(true),
163
+ /** Output directory for generated configurations */
164
+ output_dir: z.string().default('./authelia-config'),
165
+ });
166
+ export type AutheliaPluginOptionsType = z.infer<typeof authelia_plugin_options_schema>;
167
+
168
+ /**
169
+ * Generated Authelia configuration
170
+ */
171
+ export interface AutheliaConfigType {
172
+ identity_providers?: {
173
+ oidc?: {
174
+ clients?: OIDCClientConfigType[];
175
+ };
176
+ };
177
+ access_control?: {
178
+ default_policy?: AutheliaPolicyType;
179
+ rules?: AccessControlRuleType[];
180
+ };
181
+ }