Skip to content

Commit

Permalink
Add support for fine-grained access control (#514)
Browse files Browse the repository at this point in the history
Signed-off-by: Sayali Gaikawad <[email protected]>
  • Loading branch information
gaiksaya authored Dec 18, 2024
1 parent 29245f3 commit 82bf9bc
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 958 deletions.
32 changes: 18 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,21 @@ $aws secretsmanager put-secret-value \

### Executing Optional Tasks
#### Construct Props
| Name | Type | Description |
|-----------------------------------------------------------|:---------|:-----------------------------------------------------------------------------------------|
| [useSsl](#ssl-configuration) <required> | boolean | Should the Jenkins use https |
| [restrictServerAccessTo](#restricting-server-access) <required> | Ipeer | Restrict jenkins server access |
| [authType](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string | Authentication type for Jenkins login. Acceptable values: github, oidc, default |
| [ignoreResourcesFailures](#ignore-resources-failure) | boolean | Additional verification during deployment and resource startup |
| [adminUsers](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string[] | List of users with admin access during initial deployment |
| [additionalCommands](#runnning-additional-commands) | string | Additional logic that needs to be run on Master Node. The value has to be path to a file |
| [dataRetention](#data-retention) | boolean | Do you want to retain jenkins jobs and build history |
| [agentAssumeRole](#assume-role) | string | IAM role ARN to be assumed by jenkins agent nodes |
| [envVarsFilePath](#add-environment-variables) | string | Path to file containing env variables in the form of key value pairs |
| [macAgent](#mac-agents) | boolean | Add mac agents to jenkins |
| [useProdAgents](#use-production-agents) | boolean | Should jenkins server use production agents |
| [enableViews](#enable-views) | boolean | Adds Build, Test, Release and Misc views to Jenkins Dashboard . Defaults to false |
| Name | Type | Description |
|----------------------------------------------------------------------------------------|:---------|:---------------------------------------------------------------------------------------------------------------------------|
| [useSsl](#ssl-configuration) <required> | boolean | Should the Jenkins use https |
| [restrictServerAccessTo](#restricting-server-access) <required> | Ipeer | Restrict jenkins server access |
| [authType](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string | Authentication type for Jenkins login. Acceptable values: github, oidc, default |
| [ignoreResourcesFailures](#ignore-resources-failure) | boolean | Additional verification during deployment and resource startup |
| [adminUsers](#setup-authentication-using-openid-connect-oidc-or-github-authentication) | string[] | List of users with admin access during initial deployment |
| [additionalCommands](#runnning-additional-commands) | string | Additional logic that needs to be run on Master Node. The value has to be path to a file |
| [dataRetention](#data-retention) | boolean | Do you want to retain jenkins jobs and build history |
| [agentAssumeRole](#assume-role) | string | IAM role ARN to be assumed by jenkins agent nodes |
| [envVarsFilePath](#add-environment-variables) | string | Path to file containing env variables in the form of key value pairs |
| [macAgent](#mac-agents) | boolean | Add mac agents to jenkins |
| [useProdAgents](#use-production-agents) | boolean | Should jenkins server use production agents |
| [enableViews](#enable-views) | boolean | Adds Build, Test, Release and Misc views to Jenkins Dashboard . Defaults to false |
| [FineGrainedAccessSpecs](#fine-grained-access) | FineGrainedAccessSpecs | Add specifications for fine grained access contol. See [FineGrainedAccessSpecs](lib/compute/auth-config.ts) for more details |
#### SSL Configuration
1. Locate the secret manager arns in the ci-config-stack outputs
1. Update the secret value ([see docs](https://docs.aws.amazon.com/cli/latest/reference/secretsmanager/put-secret-value.html)) for the `certContentsSecret` with the certificate contents
Expand Down Expand Up @@ -264,6 +265,9 @@ Views on Jenkins dashboard allows us to classify jobs into different sections. B
The `All` is the default view and contains all the jobs.
#### Fine Grained Access
Apart from global admin and read-only access, users can be given fine-grained access for specific workflows/folder. The access is set up using role based strategy plugin. The construct props should be of type [FineGrainedAccessSpecs](./lib/compute/auth-config.ts). Check the details for specifying [patterns](https://plugins.jenkins.io/role-strategy/#plugin-content-configuring-roles). Currently, this code base only adds `builder-template` that allows to build jobs. For adding more templates, please contribute.
### Troubleshooting
#### Main Node
Useful links
Expand Down
4 changes: 4 additions & 0 deletions lib/ci-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { JenkinsMonitoring } from './monitoring/ci-alarms';
import { JenkinsExternalLoadBalancer } from './network/ci-external-load-balancer';
import { JenkinsSecurityGroups } from './security/ci-security-groups';
import { JenkinsWAF } from './security/waf';
import { FineGrainedAccessSpecs } from './compute/auth-config';

export interface CIStackProps extends StackProps {
/** Should the Jenkins use https */
Expand All @@ -52,6 +53,8 @@ export interface CIStackProps extends StackProps {
readonly enableViews?: boolean;
/** Use Production Agents */
readonly useProdAgents?: boolean;
/** Fine grain access control specifications */
readonly fineGrainedAccessSpecs?: FineGrainedAccessSpecs[];
}

function getServerAccess(serverAccessType: string, restrictServerAccessTo: string): IPeer {
Expand Down Expand Up @@ -190,6 +193,7 @@ export class CIStack extends Stack {
authCredsSecretsArn: importedAuthConfigValuesSecretBucketValue.toString(),
useSsl,
authType,
fineGrainedAccessSpecs: props?.fineGrainedAccessSpecs,
failOnCloudInitError: props?.ignoreResourcesFailures,
adminUsers: props?.adminUsers,
agentNodeSecurityGroup: this.securityGroups.agentNodeSG.securityGroupId,
Expand Down
36 changes: 32 additions & 4 deletions lib/compute/auth-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
* compatible open source license.
*/

export interface FineGrainedAccessSpecs {
users: string[],
roleName: string,
pattern: string,
templateName: string
}

export class AuthConfig {
private static readonly adminRolePermissions: string[] = [
'Overall/Administer',
Expand Down Expand Up @@ -50,10 +57,21 @@ export class AuthConfig {
'View/Read',
];

public static addOidcConfigToJenkinsYaml(yamlObject: any, authType: string, admins?: string[]): any {
private static readonly builderTemplatePermissions: string[] = [
'Job/Build',
'Job/Cancel',
'Job/Discover',
'Job/Read',
'Lockable Resources/View',
'Run/Replay',
'Metrics/View',
'View/Read',
];

public static addOidcConfigToJenkinsYaml(yamlObject: any, authType: string, admins?: string[], fineGrainedAccessItems?: FineGrainedAccessSpecs[]): any {
const jenkinsYaml: any = yamlObject;
let adminUsers: string[] = ['admin'];
const readOnlyUsers: string[] = ['anonymous'];
const readOnlyUsers: string[] = ['anonymous', 'authenticated'];

if (admins) {
adminUsers = adminUsers.concat(admins);
Expand Down Expand Up @@ -89,6 +107,10 @@ export class AuthConfig {

const rolesAndPermissions: { [x: string]: any; } = {
roleBased: {
permissionTemplates: [{
name: 'builder-template',
permissions: AuthConfig.builderTemplatePermissions,
}],
roles: {
global: [{
entries: adminUsers.map((user) => ({ user })),
Expand All @@ -103,14 +125,20 @@ export class AuthConfig {
pattern: '.*',
permissions: AuthConfig.readOnlyRolePermissions,
},

],
},
},
};

jenkinsYaml.jenkins.authorizationStrategy = rolesAndPermissions;

if (typeof fineGrainedAccessItems !== 'undefined') {
jenkinsYaml.jenkins.authorizationStrategy.roleBased.roles.items = fineGrainedAccessItems.map((item) => ({
entries: item.users.map((user) => ({ user })),
name: item.roleName,
pattern: item.pattern,
templateName: item.templateName,
}));
}
if (authType === 'github') {
jenkinsYaml.jenkins.securityRealm = githubAuthConfig;
} else {
Expand Down
6 changes: 4 additions & 2 deletions lib/compute/jenkins-main-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { join } from 'path';
import { CloudwatchAgent } from '../constructs/cloudwatch-agent';
import { AgentNodeConfig, AgentNodeNetworkProps, AgentNodeProps } from './agent-node-config';
import { EnvConfig } from './env-config';
import { AuthConfig } from './auth-config';
import { AuthConfig, FineGrainedAccessSpecs } from './auth-config';
import { ViewsConfig } from './views';

interface HttpConfigProps {
Expand All @@ -51,6 +51,7 @@ interface LoginAuthProps {
readonly authCredsSecretsArn: string;
readonly authType: string;
readonly adminUsers?: string[];
readonly fineGrainedAccessSpecs?: FineGrainedAccessSpecs[];
}

interface DataRetentionProps {
Expand Down Expand Up @@ -452,7 +453,8 @@ export class JenkinsMainNode {
agentNodeObject: AgentNodeConfig, props: AgentNodeNetworkProps, agentNode: AgentNodeProps[], macAgent: string): string {
let updatedConfig = agentNodeObject.addAgentConfigToJenkinsYaml(stack, agentNode, props, macAgent);
if (loginAuthProps.authType !== 'default') {
updatedConfig = AuthConfig.addOidcConfigToJenkinsYaml(updatedConfig, loginAuthProps.authType, loginAuthProps.adminUsers);
updatedConfig = AuthConfig.addOidcConfigToJenkinsYaml(updatedConfig, loginAuthProps.authType,
loginAuthProps.adminUsers, loginAuthProps.fineGrainedAccessSpecs);
}
if (jenkinsMainNodeProps.envVarsFilePath !== '' && jenkinsMainNodeProps.envVarsFilePath != null) {
updatedConfig = EnvConfig.addEnvConfigToJenkinsYaml(updatedConfig, jenkinsMainNodeProps.envVarsFilePath);
Expand Down
24 changes: 12 additions & 12 deletions test/ci-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { CIStack } from '../lib/ci-stack';
test('CI Stack Basic Resources', () => {
const app = new App({
context: {
useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', additionalCommands: './test/data/hello-world.py',
useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32', additionalCommands: './test/data/hello-world.py',
},
});

Expand Down Expand Up @@ -45,7 +45,7 @@ test('CI Stack Basic Resources', () => {
test('External security group is open', () => {
const app = new App({
context: {
useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all',
useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all',
},
});

Expand Down Expand Up @@ -89,7 +89,7 @@ test('External security group is open', () => {
test('External security group is restricted', () => {
const app = new App({
context: {
useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.0.0.0/24',
useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.0.0.0/24',
},
});

Expand Down Expand Up @@ -135,7 +135,7 @@ test('External security group is restricted', () => {
test('MainNode', () => {
const app = new App({
context: {
useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
useSsl: 'true', authType: 'oidc', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
},
});

Expand Down Expand Up @@ -167,7 +167,7 @@ test('MainNode', () => {
test('LoadBalancer', () => {
const app = new App({
context: {
useSsl: 'true', runWithOidc: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: 'all',
useSsl: 'true', authType: 'oidc', serverAccessType: 'ipv4', restrictServerAccessTo: 'all',
},
});

Expand All @@ -192,7 +192,7 @@ test('LoadBalancer', () => {
test('CloudwatchCpuAlarm', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
},
});

Expand All @@ -211,7 +211,7 @@ test('CloudwatchCpuAlarm', () => {
test('CloudwatchMemoryAlarm', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
},
});

Expand All @@ -230,7 +230,7 @@ test('CloudwatchMemoryAlarm', () => {
test('LoadBalancer Access Logging', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '10.10.10.10/32',
},
});

Expand Down Expand Up @@ -367,7 +367,7 @@ test('LoadBalancer Access Logging', () => {
test('WAF rules', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
},
});

Expand Down Expand Up @@ -452,7 +452,7 @@ test('WAF rules', () => {
test('Test WAF association with ALB', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
},
});

Expand All @@ -478,7 +478,7 @@ test('Test WAF association with ALB', () => {
test('Test configElement jenkins content to use X-Forwarded-For header on port 443', () => {
const app = new App({
context: {
useSsl: 'true', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
useSsl: 'true', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
},
});

Expand Down Expand Up @@ -512,7 +512,7 @@ test('Test configElement jenkins content to use X-Forwarded-For header on port 4
test('Test configElement jenkins content to use X-Forwarded-For header on port 80', () => {
const app = new App({
context: {
useSsl: 'false', runWithOidc: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
useSsl: 'false', serverAccessType: 'ipv4', restrictServerAccessTo: '0.0.0.0/0',
},
});

Expand Down
Loading

0 comments on commit 82bf9bc

Please sign in to comment.