diff --git a/Dockerfile b/Dockerfile index 52e2df74..795e2164 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,4 +52,6 @@ LABEL org.opencontainers.image.title="secureCodeBox Engine" \ org.opencontainers.image.revision=$COMMIT_ID \ org.opencontainers.image.created=$BUILD_DATE +VOLUME ["/scb-engine/config"] + ENTRYPOINT ["./init.sh"] diff --git a/scb-engine/src/main/java/io/securecodebox/engine/helper/AuthConfiguration.java b/scb-engine/src/main/java/io/securecodebox/engine/helper/AuthConfiguration.java new file mode 100644 index 00000000..d279b679 --- /dev/null +++ b/scb-engine/src/main/java/io/securecodebox/engine/helper/AuthConfiguration.java @@ -0,0 +1,211 @@ +package io.securecodebox.engine.helper; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.annotation.Validated; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import java.util.ArrayList; +import java.util.List; + +@Validated +@Configuration +@ConfigurationProperties(prefix = "securecodebox") +public class AuthConfiguration { + + private List users = new ArrayList<>(); + + private List groups = new ArrayList<>(); + + private List tenants = new ArrayList<>(); + + public static class UserConfiguration { + @NotEmpty + // See: https://docs.camunda.org/manual/7.11/update/minor/79-to-710/#whitelist-pattern-for-user-group-and-tenant-ids + // Minus the camunda admin part. As scanner users are never the camunda admin + @Pattern(regexp = "[a-zA-Z0-9]+") + private String id; + @NotEmpty + private String password; + @Email + private String email; + @NotEmpty + private String firstname = "Technical-User"; + @NotEmpty + private String lastname = "Scanner-User"; + private List groups = new ArrayList<>(); + private List tenants = new ArrayList<>(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + public List getTenants() { + return tenants; + } + + public void setTenants(List tenants) { + this.tenants = tenants; + } + } + + public static class GroupConfiguration { + @NotEmpty + // See: https://docs.camunda.org/manual/7.11/update/minor/79-to-710/#whitelist-pattern-for-user-group-and-tenant-ids + // Minus the camunda admin part. As scanner users are never the camunda admin + @Pattern(regexp = "[a-zA-Z0-9]+") + private String id; + + @NotEmpty + private String name; + + private List authorizations = new ArrayList<>(); + + public static class GroupAuthorizations { + @NotEmpty + private String resource; + + private List permissions = new ArrayList<>(); + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List permissions) { + this.permissions = permissions; + } + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getAuthorizations() { + return authorizations; + } + + public void setAuthorizations(List authorizations) { + this.authorizations = authorizations; + } + } + + public static class TenantConfiguration { + @NotEmpty + // See: https://docs.camunda.org/manual/7.11/update/minor/79-to-710/#whitelist-pattern-for-user-group-and-tenant-ids + // Minus the camunda admin part. As scanner users are never the camunda admin + @Pattern(regexp = "[a-zA-Z0-9]+") + private String id; + @NotEmpty + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public List getUsers() { + return users; + } + + public void setScanner(List users) { + this.users = users; + } + + public void setUsers(List users) { + this.users = users; + } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + + public List getTenants() { + return tenants; + } + + public void setTenants(List tenants) { + this.tenants = tenants; + } +} diff --git a/scb-engine/src/main/java/io/securecodebox/engine/helper/DefaultUserConfiguration.java b/scb-engine/src/main/java/io/securecodebox/engine/helper/DefaultUserConfiguration.java index 6a7c2c0e..45801872 100644 --- a/scb-engine/src/main/java/io/securecodebox/engine/helper/DefaultUserConfiguration.java +++ b/scb-engine/src/main/java/io/securecodebox/engine/helper/DefaultUserConfiguration.java @@ -24,12 +24,12 @@ import org.camunda.bpm.engine.ProcessEngine; import org.camunda.bpm.engine.authorization.Authorization; import org.camunda.bpm.engine.authorization.AuthorizationQuery; -import org.camunda.bpm.engine.authorization.Groups; import org.camunda.bpm.engine.authorization.Permission; import org.camunda.bpm.engine.authorization.Permissions; import org.camunda.bpm.engine.authorization.Resource; import org.camunda.bpm.engine.authorization.Resources; import org.camunda.bpm.engine.identity.Group; +import org.camunda.bpm.engine.identity.Tenant; import org.camunda.bpm.engine.identity.User; import org.camunda.bpm.spring.boot.starter.configuration.impl.AbstractCamundaConfiguration; import org.slf4j.Logger; @@ -37,6 +37,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import java.util.List; +import java.util.stream.Collectors; + /** * This configuration file adds a default user for scanner services */ @@ -46,124 +49,147 @@ public class DefaultUserConfiguration extends AbstractCamundaConfiguration { @Autowired private PropertyValueProvider properties; - private static final Logger LOG = LoggerFactory.getLogger(DefaultUserConfiguration.class); - - public static final String GROUP_SCANNER = "scanner"; - public static final String GROUP_APPROVER = "approver"; - public static final String GROUP_CI = "continuousIntegration"; + @Autowired + private AuthConfiguration authConfiguration; + private static final Logger LOG = LoggerFactory.getLogger(DefaultUserConfiguration.class); @Override public void postProcessEngineBuild(final ProcessEngine processEngine) { final IdentityService identityService = processEngine.getIdentityService(); - if(identityService.isReadOnly()) { - LOG.warn("Identity service provider is Read Only, not creating any users."); + if (identityService.isReadOnly()) { + LOG.warn("Identity service provider is ReadOnly, not creating any users."); return; } createGroups(processEngine); - setupTechnicalUserForScanner(identityService); + createTenants(identityService); + createUsers(identityService); + } + + private void createTenants(IdentityService identityService) { + for(AuthConfiguration.TenantConfiguration tenant : authConfiguration.getTenants()){ + if(identityService.createTenantQuery().tenantId(tenant.getId()).count() == 0){ + Tenant newTenant = identityService.newTenant(tenant.getId()); + newTenant.setName(tenant.getName()); + identityService.saveTenant(newTenant); + LOG.info("Created tenant {}", tenant.getId()); + } + } } - private void createGroups(final ProcessEngine processEngine){ - createGroup(processEngine.getIdentityService(), GROUP_APPROVER); - - createGroup(processEngine.getIdentityService(), GROUP_SCANNER); - createAuthorizationForGroup( - processEngine.getAuthorizationService(), - GROUP_SCANNER, - Resources.PROCESS_INSTANCE, - Permissions.READ, Permissions.UPDATE - ); - createAuthorizationForGroup( - processEngine.getAuthorizationService(), - GROUP_SCANNER, - Resources.PROCESS_DEFINITION, - Permissions.READ, Permissions.READ_INSTANCE, Permissions.UPDATE_INSTANCE - ); - - createGroup(processEngine.getIdentityService(), GROUP_CI); - createAuthorizationForGroup( - processEngine.getAuthorizationService(), - GROUP_CI, - Resources.PROCESS_DEFINITION, - Permissions.CREATE_INSTANCE, Permissions.READ, Permissions.READ_HISTORY - ); - createAuthorizationForGroup( - processEngine.getAuthorizationService(), - GROUP_CI, - Resources.PROCESS_INSTANCE, - Permissions.READ, Permissions.CREATE - ); + private void createGroups(final ProcessEngine processEngine) { + + for(AuthConfiguration.GroupConfiguration group : authConfiguration.getGroups()){ + createGroup(processEngine.getIdentityService(), group.getId(), group.getName()); + + for (AuthConfiguration.GroupConfiguration.GroupAuthorizations authorization : group.getAuthorizations()){ + createAuthorizationForGroup( + processEngine.getAuthorizationService(), + group.getId(), + Resources.valueOf(authorization.getResource()), + authorization.getPermissions().stream().map(Permissions::forName).collect(Collectors.toList()) + ); + } + } } - private void setupTechnicalUserForScanner(final IdentityService identityService) { + private void createUsers(final IdentityService identityService) { + // Deprecated single User Config final String scannerUserId = properties.getDefaultUserScannerId(); final String scannerUserPw = properties.getDefaultUserScannerPassword(); - if(scannerUserId == null || scannerUserId.isEmpty() || scannerUserPw == null || scannerUserPw.isEmpty()) { + if (scannerUserId == null || scannerUserId.isEmpty() || scannerUserPw == null || scannerUserPw.isEmpty()) { LOG.info("No environment variables provided to create technical user for scanners"); - return; + } else { + AuthConfiguration.UserConfiguration user = new AuthConfiguration.UserConfiguration(); + user.setId(scannerUserId); + user.setPassword(scannerUserPw); + user.setFirstname("Technical-User"); + user.setLastname("Scanner-User"); + user.getGroups().add("scanner"); + createUser(identityService, user); } - boolean userForScannersAlreadyExists = identityService.createUserQuery().userId(scannerUserId).count() > 0; - if(userForScannersAlreadyExists){ - LOG.info("Technical user for scanners already exists"); - } else { - LOG.info("Creating technical user for scanners"); - LOG.info("User: {}, Password: {}", scannerUserId, scannerUserPw); - createTechnicalUserForScanner(identityService, scannerUserId, scannerUserPw); - identityService.createMembership(scannerUserId, GROUP_SCANNER); + // Newer Multi User Config + for (AuthConfiguration.UserConfiguration user : authConfiguration.getUsers()) { + createUser(identityService, user); } } - private void createTechnicalUserForScanner(final IdentityService identityService, final String scannerUserId, final String scannerUserPw) { - User technicalUserForScanner = identityService.newUser(scannerUserId); - technicalUserForScanner.setPassword(scannerUserPw); - technicalUserForScanner.setFirstName("Technical-User"); - technicalUserForScanner.setLastName("Default-Scanner"); + private void createUser(final IdentityService identityService, AuthConfiguration.UserConfiguration user) { + boolean userForScannersAlreadyExists = identityService.createUserQuery().userId(user.getId()).count() > 0; + if (userForScannersAlreadyExists) { + LOG.info("User '{}' already exists", user.getId()); + return; + } + + User newUser = identityService.newUser(user.getId()); + newUser.setEmail(user.getEmail()); + newUser.setPassword(user.getPassword()); + newUser.setFirstName(user.getFirstname()); + newUser.setLastName(user.getLastname()); + + identityService.saveUser(newUser); + + for (String groupId : user.getGroups()){ + if(identityService.createGroupQuery().groupId(groupId).count() == 0){ + throw new UserConfigurationError("Tried to add user '" + user.getId() + "' to group '" + groupId + "' but the group doesn't exist. You'll need to change group of the user to a existing group or configure the group in your config so it'll get created."); + } + + identityService.createMembership(user.getId(), groupId); + LOG.info("Added user '{}' to group '{}'", user.getId(), groupId); + } - identityService.saveUser(technicalUserForScanner); + for(String tenantId : user.getTenants()){ + if(identityService.createTenantQuery().tenantId(tenantId).count() == 0){ + throw new UserConfigurationError("Tried to add user '" + user.getId() + "' to tenant '" + tenantId + "' but the tenant doesn't exist. You'll need to change tenant of the user to a existing tenant or configure the tenant in your config so it'll get created."); + } + + identityService.createTenantUserMembership(tenantId, user.getId()); + LOG.info("Added user '{}' to tenant '{}'", user.getId(), tenantId); + } } - private void createGroup(IdentityService identityService, String groupId) { - // create group + private void createGroup(IdentityService identityService, String groupId, String groupName) { if (identityService.createGroupQuery().groupId(groupId).count() == 0) { Group group = identityService.newGroup(groupId); - group.setName("SecureCodeBox " + groupId); - group.setType(Groups.GROUP_TYPE_SYSTEM); + group.setName(groupName); + group.setType("secureCodeBox"); identityService.saveGroup(group); - LOG.info("Created default secureCodeBox group: {}", group.getName()); + LOG.info("Created group: {}", group.getName()); } } - private void createAuthorizationForGroup(AuthorizationService authorizationService, String groupId, Resource resource, Permission... permissions){ - if(permissions.length == 0){ - throw new IllegalArgumentException("createAuthorizationForGroup needs at least one permission"); - } - + private void createAuthorizationForGroup(AuthorizationService authorizationService, String groupId, Resource resource, List permissions) { AuthorizationQuery authorizationQuery = authorizationService .createAuthorizationQuery() .groupIdIn(groupId) .resourceType(resource) .resourceId("*"); - for (Permission permission: permissions) { + for (Permission permission : permissions) { authorizationQuery.hasPermission(permission); } long authCounts = authorizationQuery.count(); - if(authCounts == 0){ + if (authCounts == 0) { Authorization auth = authorizationService.createNewAuthorization(Authorization.AUTH_TYPE_GRANT); auth.setGroupId(groupId); auth.setResource(resource); auth.setResourceId("*"); - for (Permission permission: permissions) { + for (Permission permission : permissions) { auth.addPermission(permission); } authorizationService.saveAuthorization(auth); - LOG.info("Created Authorization for Group {}", groupId); + LOG.info("Created authorization for group {}", groupId); + } + } + + private static class UserConfigurationError extends RuntimeException { + public UserConfigurationError(String msg){ + super(msg); } } } diff --git a/scb-engine/src/main/java/io/securecodebox/engine/helper/PropertyValueProvider.java b/scb-engine/src/main/java/io/securecodebox/engine/helper/PropertyValueProvider.java index 349d36e2..17a5d464 100644 --- a/scb-engine/src/main/java/io/securecodebox/engine/helper/PropertyValueProvider.java +++ b/scb-engine/src/main/java/io/securecodebox/engine/helper/PropertyValueProvider.java @@ -1,6 +1,7 @@ package io.securecodebox.engine.helper; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.stereotype.Component; /** @@ -35,7 +36,6 @@ public class PropertyValueProvider { @Value("${securecodebox.rest.user.scanner-default.password}") private String defaultUserScannerPassword; - /** * Default target access URI */ @@ -58,11 +58,19 @@ public String getDefaultTargetUri() { return defaultTargetUri; } - String getDefaultUserScannerId() { + @DeprecatedConfigurationProperty( + replacement = "securecodebox.users", + reason = "Using the new property multiple users can be configured, which is more flexible than only being able to create one." + ) + public String getDefaultUserScannerId() { return defaultUserScannerId; } - String getDefaultUserScannerPassword() { + @DeprecatedConfigurationProperty( + replacement = "securecodebox.users", + reason = "Using the new property multiple users can be configured, which is more flexible than only being able to create one." + ) + public String getDefaultUserScannerPassword() { return defaultUserScannerPassword; } } diff --git a/scb-engine/src/main/resources/application-dev.yaml b/scb-engine/src/main/resources/application-dev.yaml index ee8d05b0..6f1c1aa4 100644 --- a/scb-engine/src/main/resources/application-dev.yaml +++ b/scb-engine/src/main/resources/application-dev.yaml @@ -6,9 +6,94 @@ camunda.bpm: logging.level: DEBUG logging.level.io.securecodebox: DEBUG -# Configure which persistence provider you would like to choose -# - none -# - elasticsearch -securecodebox.rest.user.scanner-default: - user-id: defaultScanner - password: scan +securecodebox: + tenants: + - id: companyInternal + name: 'company Internal' + users: + - id: nmapScanner + password: 'password1' + firstname: 'nmap' + lastname: 'scanner' + groups: ['scanner'] + tenants: [] + - id: foobar + email: foo.bar@example.com + password: 'password1' + firstname: 'foo' + lastname: 'bar' + groups: ['user'] + tenants: ['companyInternal'] + groups: + - id: scanner + name: "secureCodeBox Scanner" + authorizations: + - resource: PROCESS_INSTANCE + permissions: [READ, UPDATE] + - resource: PROCESS_DEFINITION + permissions: [READ, READ_INSTANCE, UPDATE_INSTANCE] + - id: ci + name: "secureCodeBox Continuous Integration" + authorizations: + - resource: PROCESS_DEFINITION + permissions: [CREATE_INSTANCE, READ, READ_HISTORY] + - resource: PROCESS_INSTANCE + permissions: [READ, CREATE] + - id: user + name: "secureCodeBox User" + authorizations: + - resource: APPLICATION + permissions: [ACCESS] + - resource: DASHBOARD + permissions: [ALL] + - resource: DEPLOYMENT + permissions: [READ] + - resource: FILTER + permissions: [ALL] + - resource: PROCESS_DEFINITION + permissions: [CREATE_INSTANCE, READ, READ_HISTORY] + - resource: PROCESS_INSTANCE + permissions: [ALL] + - resource: TASK + permissions: [ALL] + - id: admin + name: "secureCodeBox Admin" + authorizations: + - resource: APPLICATION + permissions: [ALL] + - resource: AUTHORIZATION + permissions: [ALL] + - resource: BATCH + permissions: [ALL] + - resource: DASHBOARD + permissions: [ALL] + - resource: DECISION_DEFINITION + permissions: [ALL] + - resource: DECISION_REQUIREMENTS_DEFINITION + permissions: [ALL] + - resource: FILTER + permissions: [ALL] + - resource: GROUP + permissions: [ALL] + - resource: GROUP_MEMBERSHIP + permissions: [ALL] + - resource: PROCESS_DEFINITION + permissions: [ALL] + - resource: PROCESS_INSTANCE + permissions: [ALL] + - resource: REPORT + permissions: [ALL] + - resource: TASK + permissions: [ALL] + - resource: TENANT + permissions: [ALL] + - resource: TENANT + permissions: [ALL] + - resource: TENANT_MEMBERSHIP + permissions: [ALL] + - resource: USER + permissions: [ALL] + rest.user: + scanner-default: + user-id: defaultScanner + password: scanner diff --git a/scb-engine/src/main/resources/application.yaml b/scb-engine/src/main/resources/application.yaml index cc7cd809..332d6776 100644 --- a/scb-engine/src/main/resources/application.yaml +++ b/scb-engine/src/main/resources/application.yaml @@ -60,8 +60,81 @@ securecodebox.default.context: BodgeIT # - none securecodebox.rest.auth: basic auth -# Configure a technical user for the scanner services. This user allows the scanner services to authenticate on the engines rest api. -# (If not set as environment variable, a user has to be added manually in the camunda ui.) -securecodebox.rest.user.scanner-default: - user-id: ${SECURECODEBOX_USER_SCANNER:} - password: ${SECURECODEBOX_USER_SCANNER_PW:} +securecodebox: + tenants: [] + users: [] + # Documentation on Authorization resources and permission of Camunda types are documented here: + # https://docs.camunda.org/manual/7.12/user-guide/process-engine/authorization-service/#basic-principles + groups: + - id: scanner + name: "secureCodeBox Scanner" + authorizations: + - resource: PROCESS_INSTANCE + permissions: [READ, UPDATE] + - resource: PROCESS_DEFINITION + permissions: [READ, READ_INSTANCE, UPDATE_INSTANCE] + - id: ci + name: "secureCodeBox Continuous Integration" + authorizations: + - resource: PROCESS_DEFINITION + permissions: [CREATE_INSTANCE, READ, READ_HISTORY] + - resource: PROCESS_INSTANCE + permissions: [READ, CREATE] + - id: user + name: "secureCodeBox User" + authorizations: + - resource: APPLICATION + permissions: [ACCESS] + - resource: DASHBOARD + permissions: [ALL] + - resource: DEPLOYMENT + permissions: [READ] + - resource: FILTER + permissions: [ALL] + - resource: PROCESS_DEFINITION + permissions: [CREATE_INSTANCE, READ, READ_HISTORY] + - resource: PROCESS_INSTANCE + permissions: [ALL] + - resource: TASK + permissions: [ALL] + - id: admin + name: "secureCodeBox Admin" + authorizations: + - resource: APPLICATION + permissions: [ALL] + - resource: AUTHORIZATION + permissions: [ALL] + - resource: BATCH + permissions: [ALL] + - resource: DASHBOARD + permissions: [ALL] + - resource: DECISION_DEFINITION + permissions: [ALL] + - resource: DECISION_REQUIREMENTS_DEFINITION + permissions: [ALL] + - resource: FILTER + permissions: [ALL] + - resource: GROUP + permissions: [ALL] + - resource: GROUP_MEMBERSHIP + permissions: [ALL] + - resource: PROCESS_DEFINITION + permissions: [ALL] + - resource: PROCESS_INSTANCE + permissions: [ALL] + - resource: REPORT + permissions: [ALL] + - resource: TASK + permissions: [ALL] + - resource: TENANT + permissions: [ALL] + - resource: TENANT + permissions: [ALL] + - resource: TENANT_MEMBERSHIP + permissions: [ALL] + - resource: USER + permissions: [ALL] + rest.user: + scanner-default: + user-id: ${SECURECODEBOX_USER_SCANNER:} + password: ${SECURECODEBOX_USER_SCANNER_PW:}