diff --git a/doc/Manuals/ug-syspar_x-road_v6_system_parameters.md b/doc/Manuals/ug-syspar_x-road_v6_system_parameters.md index 1758b90680..ad93a49d7f 100644 --- a/doc/Manuals/ug-syspar_x-road_v6_system_parameters.md +++ b/doc/Manuals/ug-syspar_x-road_v6_system_parameters.md @@ -1,6 +1,6 @@ # X-Road: System Parameters User Guide -Version: 2.74 +Version: 2.75 Doc. ID: UG-SYSPAR @@ -85,6 +85,7 @@ Doc. ID: UG-SYSPAR | 05.06.2023 | 2.72 | Update global configuration generation rate parameter | Andres Rosenthal | | 15.06.2023 | 2.73 | Added *strict-identifier-checks* parameter | Madis Loitmaa | | 13.06.2023 | 2.74 | Add configurable max request size constrains for central server admin service. | Andres Rosenthal | +| 13.06.2023 | 2.75 | Added new *complementary-user-role-mappings* parameters | Andres Rosenthal | ## Table of Contents @@ -415,23 +416,24 @@ the message log. ### 3.9 Management REST API parameters: `[proxy-ui-api]` -| **Parameter** | **Default value** | **Description** | -|---------------------------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ssl-properties | /etc/xroad/ssl.properties | Absolute path to file which overrides the default properties of the SSL connections to REST API. | -| key-management-api-whitelist | 127.0.0.0/8, ::1 | Comma-separated list of plain IP addresses or address ranges in CIDR notation, which are allowed to call key management endpoints using basic authentication | -| regular-api-whitelist | 0.0.0.0/0, ::/0 | Comma-separated list of plain IP addresses or address ranges in CIDR notation, which are allowed to call regular endpoints using api key authentication | -| wsdl-validator-command | | The command to validate the given X-Road service WSDL. The command script must:
a) read the WSDL from the URI given as an argument,
b) return exit code 0 on success,
c) return exit code 0 and write warnings to the standard error (*stderr*), if warnings occurs,
d) return exit code other then 0 and write error messages to the standard error (*stderr*), if errors occurs.
Defaults to no operation. | -| auth-cert-reg-signature-digest-algorithm-id | SHA-512 | Signature digest algorithm used for generating authentication certificate registration request.
Possible values are
- SHA-256,
- SHA-384,
- SHA-512. | -| auto-update-timestamp-service-url | false | If enabled, makes the security server update the timestamping service URLs when they are changed on the central server. In case there are multiple timestamping services with the same name, the update will not be done and a warning is logged instead. | -| request-size-limit-regular | 50KB | Maximum size of Management REST API requests | -| request-size-limit-binary-upload | 10MB | Maximum size of Management REST API requests for file uploads | -| rate-limit-requests-per-second | 20 | Management REST API request rate limit per second. Rate limits apply per each requesting IP. | -| rate-limit-requests-per-minute | 600 | Management REST API request rate limit per minute. Rate limits apply per each requesting IP. | -| rate-limit-cache-size | 10000 | Controls how many IP addresses can be remembered in the rate-limit cache Tradeoff between memory usage and protection from a large attack. | -| allowed-hostnames | | Determines which hostnames are allowed to be used in "Host" header of the HTTP requests. Any hostname is allowed when left unspecified. | -| cache-default-ttl | 60 | Configures default cache expiration in seconds. Cache is hit during authentication requests. Setting the value to -1 disables the cache. | -| cache-api-key-ttl | 60 | Configures Api Key cache expiration in seconds. Can be used by various api services. Setting the value to -1 disables the cache. | -| strict-identifier-checks | true* | Configures identifier input validation (member code, subsystem code etc).
Values:
`true`- identifiers must mach pattern `^[a-zA-Z0-9'()+,-.=?]*`.
`false`- identfiers must not contain colon (`:`), semicolon (`;`), slashes (`/\`), percent (`%`) or control characters. | +| **Parameter** | **Default value** | **Description** | +|---------------------------------------------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ssl-properties | /etc/xroad/ssl.properties | Absolute path to file which overrides the default properties of the SSL connections to REST API. | +| key-management-api-whitelist | 127.0.0.0/8, ::1 | Comma-separated list of plain IP addresses or address ranges in CIDR notation, which are allowed to call key management endpoints using basic authentication | +| regular-api-whitelist | 0.0.0.0/0, ::/0 | Comma-separated list of plain IP addresses or address ranges in CIDR notation, which are allowed to call regular endpoints using api key authentication | +| wsdl-validator-command | | The command to validate the given X-Road service WSDL. The command script must:
a) read the WSDL from the URI given as an argument,
b) return exit code 0 on success,
c) return exit code 0 and write warnings to the standard error (*stderr*), if warnings occurs,
d) return exit code other then 0 and write error messages to the standard error (*stderr*), if errors occurs.
Defaults to no operation. | +| auth-cert-reg-signature-digest-algorithm-id | SHA-512 | Signature digest algorithm used for generating authentication certificate registration request.
Possible values are
- SHA-256,
- SHA-384,
- SHA-512. | +| auto-update-timestamp-service-url | false | If enabled, makes the security server update the timestamping service URLs when they are changed on the central server. In case there are multiple timestamping services with the same name, the update will not be done and a warning is logged instead. | +| request-size-limit-regular | 50KB | Maximum size of Management REST API requests | +| request-size-limit-binary-upload | 10MB | Maximum size of Management REST API requests for file uploads | +| rate-limit-requests-per-second | 20 | Management REST API request rate limit per second. Rate limits apply per each requesting IP. | +| rate-limit-requests-per-minute | 600 | Management REST API request rate limit per minute. Rate limits apply per each requesting IP. | +| rate-limit-cache-size | 10000 | Controls how many IP addresses can be remembered in the rate-limit cache Tradeoff between memory usage and protection from a large attack. | +| allowed-hostnames | | Determines which hostnames are allowed to be used in "Host" header of the HTTP requests. Any hostname is allowed when left unspecified. | +| cache-default-ttl | 60 | Configures default cache expiration in seconds. Cache is hit during authentication requests. Setting the value to -1 disables the cache. | +| cache-api-key-ttl | 60 | Configures Api Key cache expiration in seconds. Can be used by various api services. Setting the value to -1 disables the cache. | +| strict-identifier-checks | true* | Configures identifier input validation (member code, subsystem code etc).
Values:
`true`- identifiers must mach pattern `^[a-zA-Z0-9'()+,-.=?]*`.
`false`- identfiers must not contain colon (`:`), semicolon (`;`), slashes (`/\`), percent (`%`) or control characters. | +| complementary-user-role-mappings | | Configures additional UNIX groups mapped to X-Road user roles. **Example:** *XROAD_SECURITY_OFFICER=group1,group2*
**Note that following configurations are preconfigured and cannot be redefined:**
*XROAD_SECURITY_OFFICER=xroad-security-officer*
*XROAD_REGISTRATION_OFFICER=xroad-registration-officer*
*XROAD_SERVICE_ADMINISTRATOR=xroad-service-administrator*
*XROAD_SYSTEM_ADMINISTRATOR=xroad-system-administrator*
*XROAD_SECURITYSERVER_OBSERVER=xroad-securityserver-observer* | > **NOTE**: `strict-identifier-checks` default value is true for new installations starting from version 7.3.0. It is > set to `false` in `local.ini` during upgrade process if version installed before upgrade is less than 7.3.0. @@ -512,6 +514,7 @@ For instructions on how to change the parameter values, see section [Changing th | strict-identifier-checks | true* | Configures identifier input validation (member code, subsystem code etc).
Values:
`true`- identifiers must mach pattern `^[a-zA-Z0-9'()+,-.=?]*`.
`false`- identfiers must not contain colon (`:`), semicolon (`;`), slashes (`/\`), percent (`%`) or control characters. | | request-size-limit-regular | 50KB | Maximum size of Management REST API requests | | request-size-limit-binary-upload | 10MB | Maximum size of Management REST API requests for file uploads | +| complementary-user-role-mappings | | Configures additional UNIX groups mapped to X-Road user roles. **Example:** *XROAD_SECURITY_OFFICER=group1,group2*
**Note that following configurations are preconfigured and cannot be redefined:**
*XROAD_SECURITY_OFFICER=xroad-security-officer*
*XROAD_REGISTRATION_OFFICER=xroad-registration-officer*
*XROAD_SYSTEM_ADMINISTRATOR=xroad-system-administrator* | > **NOTE**: `strict-identifier-checks` default value is true for new installations starting from version 7.3.0. It is > set to `false` in `local.ini` during upgrade process if version installed before upgrade is less than 7.3.0. diff --git a/src/central-server/admin-service/core/src/main/java/org/niis/xroad/cs/admin/core/config/AdminServiceProperties.java b/src/central-server/admin-service/core/src/main/java/org/niis/xroad/cs/admin/core/config/AdminServiceProperties.java index 2010e0bd76..be6510f750 100644 --- a/src/central-server/admin-service/core/src/main/java/org/niis/xroad/cs/admin/core/config/AdminServiceProperties.java +++ b/src/central-server/admin-service/core/src/main/java/org/niis/xroad/cs/admin/core/config/AdminServiceProperties.java @@ -34,12 +34,21 @@ import org.niis.xroad.restapi.config.ApiCachingConfiguration; import org.niis.xroad.restapi.config.IdentifierValidationConfiguration; import org.niis.xroad.restapi.config.LimitRequestSizesFilter; +import org.niis.xroad.restapi.config.UserRoleConfig; +import org.niis.xroad.restapi.domain.Role; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.util.unit.DataSize; +import java.util.EnumMap; import java.util.List; import java.util.Set; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static org.niis.xroad.restapi.domain.Role.XROAD_REGISTRATION_OFFICER; +import static org.niis.xroad.restapi.domain.Role.XROAD_SECURITY_OFFICER; +import static org.niis.xroad.restapi.domain.Role.XROAD_SYSTEM_ADMINISTRATOR; /** * Admin service configuration properties. @@ -59,7 +68,8 @@ public class AdminServiceProperties implements IpThrottlingFilterConfig, ApiCachingConfiguration.Config, LimitRequestSizesFilter.Config, IdentifierValidationConfiguration.Config, - AllowedFilesConfig { + AllowedFilesConfig, + UserRoleConfig { /** * Controls the rate of global configuration generation in seconds. @@ -145,4 +155,24 @@ public class AdminServiceProperties implements IpThrottlingFilterConfig, /** Configures Api file upload request size limit. */ private DataSize requestSizeLimitBinaryUpload; + + /** + * Configures additional UNIX groups mapped to X-Road user roles. + */ + private EnumMap> complementaryUserRoleMappings; + + @Override + public EnumMap> getUserRoleMappings() { + EnumMap> userRoleMappings = new EnumMap<>(Role.class); + userRoleMappings.put(XROAD_SECURITY_OFFICER, List.of("xroad-security-officer")); + userRoleMappings.put(XROAD_REGISTRATION_OFFICER, List.of("xroad-registration-officer")); + userRoleMappings.put(XROAD_SYSTEM_ADMINISTRATOR, List.of("xroad-system-administrator")); + + if (complementaryUserRoleMappings != null) { + complementaryUserRoleMappings.forEach((role, groups) -> userRoleMappings.merge(role, groups, + (a, b) -> Stream.concat(a.stream(), b.stream()).collect(toList()))); + } + + return userRoleMappings; + } } diff --git a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/ApiKeyAuthenticationManager.java b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/ApiKeyAuthenticationManager.java index 58769d33b6..f54e344e8e 100644 --- a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/ApiKeyAuthenticationManager.java +++ b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/ApiKeyAuthenticationManager.java @@ -31,7 +31,6 @@ import org.niis.xroad.restapi.domain.PersistentApiKeyType; import org.niis.xroad.restapi.domain.Role; import org.niis.xroad.restapi.service.ApiKeyService; -import org.niis.xroad.restapi.util.SecurityHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.authentication.AuthenticationManager; @@ -57,7 +56,6 @@ public class ApiKeyAuthenticationManager implements AuthenticationManager { private final GrantedAuthorityMapper permissionMapper; private final AuthenticationIpWhitelist authenticationIpWhitelist; private final AuditEventLoggingFacade auditEventLoggingFacade; - private final SecurityHelper securityHelper; @Autowired public ApiKeyAuthenticationManager(ApiKeyAuthenticationHelper apiKeyAuthenticationHelper, @@ -65,13 +63,12 @@ public ApiKeyAuthenticationManager(ApiKeyAuthenticationHelper apiKeyAuthenticati GrantedAuthorityMapper permissionMapper, @Qualifier(AuthenticationIpWhitelist.REGULAR_API_WHITELIST) AuthenticationIpWhitelist authenticationIpWhitelist, - AuditEventLoggingFacade auditEventLoggingFacade, SecurityHelper securityHelper) { + AuditEventLoggingFacade auditEventLoggingFacade) { this.apiKeyAuthenticationHelper = apiKeyAuthenticationHelper; this.authenticationHeaderDecoder = authenticationHeaderDecoder; this.permissionMapper = permissionMapper; this.authenticationIpWhitelist = authenticationIpWhitelist; this.auditEventLoggingFacade = auditEventLoggingFacade; - this.securityHelper = securityHelper; } @Override @@ -90,11 +87,8 @@ public Authentication authenticate(Authentication authentication) throws Authent throw new BadCredentialsException("Unknown problem when getting API key", e); } Set roles = key.getRoles(); - PreAuthenticatedAuthenticationToken authenticationWithGrants = - new PreAuthenticatedAuthenticationToken(createPrincipal(key), - authentication.getCredentials(), - permissionMapper.getAuthorities(roles)); - return authenticationWithGrants; + return new PreAuthenticatedAuthenticationToken( + createPrincipal(key), authentication.getCredentials(), permissionMapper.getAuthorities(roles)); } catch (Exception e) { auditEventLoggingFacade.auditLogFail(RestApiAuditEvent.API_KEY_AUTHENTICATION, e); throw e; diff --git a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProvider.java b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProvider.java index 6f399a8d25..e033af331e 100644 --- a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProvider.java +++ b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProvider.java @@ -25,6 +25,7 @@ */ package org.niis.xroad.restapi.auth; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jvnet.libpam.PAM; import org.jvnet.libpam.PAMException; @@ -32,7 +33,6 @@ import org.niis.xroad.restapi.config.audit.AuditEventLoggingFacade; import org.niis.xroad.restapi.config.audit.RestApiAuditEvent; import org.niis.xroad.restapi.domain.Role; -import org.niis.xroad.restapi.util.SecurityHelper; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.BadCredentialsException; @@ -41,11 +41,14 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; + +import static java.util.stream.Collectors.toSet; /** * PAM authentication provider. @@ -56,6 +59,7 @@ * Authentication is limited with an IP whitelist. */ @Slf4j +@RequiredArgsConstructor public class PamAuthenticationProvider implements AuthenticationProvider { // from PAMLoginModule @@ -66,52 +70,20 @@ public class PamAuthenticationProvider implements AuthenticationProvider { private final AuthenticationIpWhitelist authenticationIpWhitelist; private final GrantedAuthorityMapper grantedAuthorityMapper; + private final EnumMap> userRoleMappings; private final RestApiAuditEvent loginEvent; // login event to audit log private final AuditEventLoggingFacade auditEventLoggingFacade; - private final SecurityHelper securityHelper; - - /** - * constructor - * @param authenticationIpWhitelist whitelist that limits the authentication - */ - public PamAuthenticationProvider(AuthenticationIpWhitelist authenticationIpWhitelist, - GrantedAuthorityMapper grantedAuthorityMapper, RestApiAuditEvent loginEvent, - AuditEventLoggingFacade auditEventLoggingFacade, SecurityHelper securityHelper) { - this.authenticationIpWhitelist = authenticationIpWhitelist; - this.grantedAuthorityMapper = grantedAuthorityMapper; - this.loginEvent = loginEvent; - this.auditEventLoggingFacade = auditEventLoggingFacade; - this.securityHelper = securityHelper; - } - - /** - * users with these groups are allowed access - */ - private static final Set ALLOWED_GROUP_NAMES = Collections.unmodifiableSet( - Arrays.stream(Role.values()) - .map(Role::getLinuxGroupName) - .collect(Collectors.toSet())); @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - boolean success = false; - String username = "unknown user"; - Exception caughException = null; - + String username = String.valueOf(authentication.getPrincipal()); try { - username = String.valueOf(authentication.getPrincipal()); Authentication result = doAuthenticateInternal(authentication, username); - success = true; + auditEventLoggingFacade.auditLogSuccess(loginEvent, username); return result; } catch (Exception e) { - caughException = e; + auditEventLoggingFacade.auditLogFail(loginEvent, e, username); throw e; - } finally { - if (success) { - auditEventLoggingFacade.auditLogSuccess(loginEvent, username); - } else { - auditEventLoggingFacade.auditLogFail(loginEvent, caughException, username); - } } } @@ -127,15 +99,13 @@ private Authentication doAuthenticateInternal(Authentication authentication, Str try { UnixUser user = pam.authenticate(username, password); Set groups = user.getGroups(); - Set matchingGroups = groups.stream() - .filter(ALLOWED_GROUP_NAMES::contains) - .collect(Collectors.toSet()); - if (matchingGroups.isEmpty()) { + Collection xroadRoles = userRoleMappings.entrySet().stream() + .filter(roleGroupMapping -> !Collections.disjoint(roleGroupMapping.getValue(), groups)) + .map(Map.Entry::getKey) + .collect(toSet()); + if (xroadRoles.isEmpty()) { throw new AuthenticationServiceException("user hasn't got any required groups"); } - Collection xroadRoles = matchingGroups.stream() - .map(groupName -> Role.getForGroupName(groupName).get()) - .collect(Collectors.toSet()); Set grants = grantedAuthorityMapper.getAuthorities(xroadRoles); return new UsernamePasswordAuthenticationToken(user.getUserName(), authentication.getCredentials(), grants); } catch (PAMException e) { diff --git a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProviderConfig.java b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProviderConfig.java index afc043601e..c5a7ebaa8f 100644 --- a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProviderConfig.java +++ b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/auth/PamAuthenticationProviderConfig.java @@ -24,10 +24,11 @@ */ package org.niis.xroad.restapi.auth; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.niis.xroad.restapi.config.UserRoleConfig; import org.niis.xroad.restapi.config.audit.AuditEventLoggingFacade; import org.niis.xroad.restapi.config.audit.RestApiAuditEvent; -import org.niis.xroad.restapi.util.SecurityHelper; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -45,6 +46,7 @@ @Slf4j @Configuration @Profile("!devtools-test-auth") +@RequiredArgsConstructor public class PamAuthenticationProviderConfig { public static final String KEY_MANAGEMENT_PAM_AUTHENTICATION = "keyManagementPam"; @@ -56,17 +58,7 @@ public class PamAuthenticationProviderConfig { private final GrantedAuthorityMapper grantedAuthorityMapper; private final AuditEventLoggingFacade auditEventLoggingFacade; - private final SecurityHelper securityHelper; - - /** - * constructor - */ - public PamAuthenticationProviderConfig(GrantedAuthorityMapper grantedAuthorityMapper, - AuditEventLoggingFacade auditEventLoggingFacade, SecurityHelper securityHelper) { - this.grantedAuthorityMapper = grantedAuthorityMapper; - this.auditEventLoggingFacade = auditEventLoggingFacade; - this.securityHelper = securityHelper; - } + private final UserRoleConfig userRoleConfig; /** * PAM authentication for form login, with corresponding IP whitelist @@ -76,8 +68,9 @@ public PamAuthenticationProviderConfig(GrantedAuthorityMapper grantedAuthorityMa public PamAuthenticationProvider formLoginPamAuthentication() { AuthenticationIpWhitelist formLoginWhitelist = new AuthenticationIpWhitelist(); formLoginWhitelist.setWhitelistEntries(FORM_LOGIN_IP_WHITELIST); - return new PamAuthenticationProvider(formLoginWhitelist, grantedAuthorityMapper, RestApiAuditEvent.FORM_LOGIN, - auditEventLoggingFacade, securityHelper); + return new PamAuthenticationProvider( + formLoginWhitelist, grantedAuthorityMapper, userRoleConfig.getUserRoleMappings(), RestApiAuditEvent.FORM_LOGIN, + auditEventLoggingFacade); } /** @@ -87,8 +80,8 @@ public PamAuthenticationProvider formLoginPamAuthentication() { @Bean(KEY_MANAGEMENT_PAM_AUTHENTICATION) public PamAuthenticationProvider keyManagementWhitelist( @Qualifier(KEY_MANAGEMENT_API_WHITELIST) AuthenticationIpWhitelist keyManagementWhitelist) { - return new PamAuthenticationProvider(keyManagementWhitelist, grantedAuthorityMapper, - RestApiAuditEvent.KEY_MANAGEMENT_PAM_LOGIN, auditEventLoggingFacade, securityHelper); + return new PamAuthenticationProvider(keyManagementWhitelist, grantedAuthorityMapper, userRoleConfig.getUserRoleMappings(), + RestApiAuditEvent.KEY_MANAGEMENT_PAM_LOGIN, auditEventLoggingFacade); } } diff --git a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/config/UserRoleConfig.java b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/config/UserRoleConfig.java new file mode 100644 index 0000000000..873d1709df --- /dev/null +++ b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/config/UserRoleConfig.java @@ -0,0 +1,40 @@ +/** + * The MIT License + * Copyright (c) 2019- Nordic Institute for Interoperability Solutions (NIIS) + * Copyright (c) 2018 Estonian Information System Authority (RIA), + * Nordic Institute for Interoperability Solutions (NIIS), Population Register Centre (VRK) + * Copyright (c) 2015-2017 Estonian Information System Authority (RIA), Population Register Centre (VRK) + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.niis.xroad.restapi.config; + +import org.niis.xroad.restapi.domain.Role; + +import java.util.EnumMap; +import java.util.List; + +public interface UserRoleConfig { + + /** + * Configures UNIX groups mapped to X-Road user roles. + */ + EnumMap> getUserRoleMappings(); + +} diff --git a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/InvalidRoleNameException.java b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/InvalidRoleNameException.java index 56e121dc11..c1ea8ab487 100644 --- a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/InvalidRoleNameException.java +++ b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/InvalidRoleNameException.java @@ -32,4 +32,8 @@ public class InvalidRoleNameException extends Exception { public InvalidRoleNameException(String s) { super(s); } + + public InvalidRoleNameException(String s, Throwable cause) { + super(s, cause); + } } diff --git a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/Role.java b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/Role.java index cad838d03d..6e9febc7a7 100644 --- a/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/Role.java +++ b/src/common/common-admin-api/src/main/java/org/niis/xroad/restapi/domain/Role.java @@ -25,13 +25,10 @@ */ package org.niis.xroad.restapi.domain; -import com.google.common.collect.MoreCollectors; import lombok.Getter; -import java.util.Arrays; import java.util.Collection; import java.util.EnumSet; -import java.util.Optional; import java.util.Set; /** @@ -39,58 +36,12 @@ */ @Getter public enum Role { - XROAD_SECURITY_OFFICER(1, "xroad-security-officer"), - XROAD_REGISTRATION_OFFICER(2, "xroad-registration-officer"), - XROAD_SERVICE_ADMINISTRATOR(3, "xroad-service-administrator"), - XROAD_SYSTEM_ADMINISTRATOR(4, "xroad-system-administrator"), - XROAD_SECURITYSERVER_OBSERVER(5, "xroad-securityserver-observer"), - //management service does not exist as a linux group - XROAD_MANAGEMENT_SERVICE(6, null); - - /** - * Some unfortunate extra boilerplate, since role names in e.g. @RolesAllowed - * annotations need to be constants. Keep this in sync with the actual - * enum values. - * Alternative is to use approach like https://stackoverflow.com/a/54289956/1469083 - * and non-standard annotations - */ - public final class Names { - public static final String XROAD_SECURITY_OFFICER = "XROAD_SECURITY_OFFICER"; - public static final String XROAD_REGISTRATION_OFFICER = "XROAD_REGISTRATION_OFFICER"; - public static final String XROAD_SERVICE_ADMINISTRATOR = "XROAD_SERVICE_ADMINISTRATOR"; - public static final String XROAD_SYSTEM_ADMINISTRATOR = "XROAD_SYSTEM_ADMINISTRATOR"; - public static final String XROAD_SECURITYSERVER_OBSERVER = "XROAD_SECURITYSERVER_OBSERVER"; - private Names() { - } - } - - private final String linuxGroupName; - // primary key in db - private final int key; - - Role(int key, String linuxGroupName) { - this.key = key; - this.linuxGroupName = linuxGroupName; - } - - /** - * get key - * @return - */ - public int getKey() { - return key; - } - - /** - * return Role matching given key - * @param key - * @return - */ - public static Role getForKey(int key) { - return Arrays.stream(values()) - .filter(role -> role.getKey() == key) - .collect(MoreCollectors.onlyElement()); - } + XROAD_SECURITY_OFFICER, + XROAD_REGISTRATION_OFFICER, + XROAD_SERVICE_ADMINISTRATOR, + XROAD_SYSTEM_ADMINISTRATOR, + XROAD_SECURITYSERVER_OBSERVER, + XROAD_MANAGEMENT_SERVICE; /** * @return name which follows the "ROLE_" + name convention @@ -99,45 +50,22 @@ public String getGrantedAuthorityName() { return "ROLE_" + name(); } - /** - * return Role matching given linuxGroupName, if any - * @param linuxGroupName - * @return - */ - public static Optional getForGroupName(String linuxGroupName) { - return Arrays.stream(values()) - .filter(role -> role.linuxGroupName.equals(linuxGroupName)) - .findFirst(); - } - /** * Return Roles matching the given role names. Throws InvalidRoleNameException * if matching Role for some name does not exist. * @param names - * @return + * @return set of matching roles */ public static Set getForNames(Collection names) throws InvalidRoleNameException { Set roles = EnumSet.noneOf(Role.class); for (String name: names) { - if (!Role.contains(name)) { - throw new InvalidRoleNameException("invalid role " + name); + try { + Role.valueOf(name); + } catch (IllegalArgumentException e) { + throw new InvalidRoleNameException("Invalid role: " + name, e); } roles.add(Role.valueOf(name)); } return roles; } - - /** - * Tells if parameter string is one of Role names - * @param name - * @return - */ - public static boolean contains(String name) { - for (Role role: Role.values()) { - if (role.name().equals(name)) { - return true; - } - } - return false; - } } diff --git a/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/auth/RoleTest.java b/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/auth/RoleTest.java index 8ec0de030f..b5d591083e 100644 --- a/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/auth/RoleTest.java +++ b/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/auth/RoleTest.java @@ -26,25 +26,30 @@ package org.niis.xroad.restapi.auth; import org.junit.jupiter.api.Test; +import org.niis.xroad.restapi.domain.InvalidRoleNameException; import org.niis.xroad.restapi.domain.Role; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import java.util.Set; -/** - * test role - */ -public class RoleTest { +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.niis.xroad.restapi.domain.Role.XROAD_SERVICE_ADMINISTRATOR; + +class RoleTest { + + @Test + void getGrantedAuthorityName() { + String result = XROAD_SERVICE_ADMINISTRATOR.getGrantedAuthorityName(); + assertThat(result).isEqualTo("ROLE_" + XROAD_SERVICE_ADMINISTRATOR.name()); + } @Test - public void test() { - Role role = Role.getForKey(3); - assertEquals("XROAD_SERVICE_ADMINISTRATOR", role.name()); + void getForNames() throws InvalidRoleNameException { + Set result = Role.getForNames(Set.of(XROAD_SERVICE_ADMINISTRATOR.name())); + assertThat(result).containsOnly(XROAD_SERVICE_ADMINISTRATOR); - try { - role = Role.getForKey(10); - fail("should throw exception"); - } catch (Exception expected) { - } + assertThatThrownBy(() -> Role.getForNames(Set.of("INVALID_ROLE"))) + .isInstanceOf(InvalidRoleNameException.class) + .hasMessage("Invalid role: INVALID_ROLE"); } } diff --git a/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/controller/ApiKeysControllerTest.java b/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/controller/ApiKeysControllerTest.java index d75e9c6949..1fc911dc37 100644 --- a/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/controller/ApiKeysControllerTest.java +++ b/src/common/common-admin-api/src/test/java/org/niis/xroad/restapi/controller/ApiKeysControllerTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.niis.xroad.restapi.config.AllowedHostnamesConfig; +import org.niis.xroad.restapi.config.UserRoleConfig; import org.niis.xroad.restapi.converter.PublicApiKeyDataConverter; import org.niis.xroad.restapi.domain.InvalidRoleNameException; import org.niis.xroad.restapi.domain.PersistentApiKeyType; @@ -64,6 +65,8 @@ class ApiKeysControllerTest extends AbstractSpringMvcTest { public PublicApiKeyDataConverter publicApiKeyDataConverter; @MockBean private AllowedHostnamesConfig allowedHostnamesConfig; + @MockBean + private UserRoleConfig userRoleConfig; private static final String AUTHORITY_WRONG = "AUTHORITY_WRONG"; private static final String AUTHORITY_CREATE_API_KEY = "CREATE_API_KEY"; diff --git a/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/config/AdminServiceProperties.java b/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/config/AdminServiceProperties.java index 131302307a..68a2ee19a0 100644 --- a/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/config/AdminServiceProperties.java +++ b/src/security-server/admin-service/application/src/main/java/org/niis/xroad/securityserver/restapi/config/AdminServiceProperties.java @@ -33,11 +33,22 @@ import org.niis.xroad.restapi.config.ApiCachingConfiguration; import org.niis.xroad.restapi.config.IdentifierValidationConfiguration; import org.niis.xroad.restapi.config.LimitRequestSizesFilter; +import org.niis.xroad.restapi.config.UserRoleConfig; +import org.niis.xroad.restapi.domain.Role; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.util.unit.DataSize; +import java.util.EnumMap; import java.util.List; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static org.niis.xroad.restapi.domain.Role.XROAD_REGISTRATION_OFFICER; +import static org.niis.xroad.restapi.domain.Role.XROAD_SECURITYSERVER_OBSERVER; +import static org.niis.xroad.restapi.domain.Role.XROAD_SECURITY_OFFICER; +import static org.niis.xroad.restapi.domain.Role.XROAD_SERVICE_ADMINISTRATOR; +import static org.niis.xroad.restapi.domain.Role.XROAD_SYSTEM_ADMINISTRATOR; /** * Admin service configuration properties. @@ -51,7 +62,8 @@ public class AdminServiceProperties implements IpThrottlingFilterConfig, AllowedHostnamesConfig, ApiCachingConfiguration.Config, LimitRequestSizesFilter.Config, - IdentifierValidationConfiguration.Config { + IdentifierValidationConfiguration.Config, + UserRoleConfig { /** * Controls how many requests from an IP address are allowed per minute. @@ -108,5 +120,27 @@ public class AdminServiceProperties implements IpThrottlingFilterConfig, /** Configures Api file upload request size limit. */ private DataSize requestSizeLimitBinaryUpload; + + /** + * Configures additional UNIX groups mapped to X-Road user roles. + */ + private EnumMap> complementaryUserRoleMappings; + + @Override + public EnumMap> getUserRoleMappings() { + EnumMap> userRoleMappings = new EnumMap<>(Role.class); + userRoleMappings.put(XROAD_SECURITY_OFFICER, List.of("xroad-security-officer")); + userRoleMappings.put(XROAD_REGISTRATION_OFFICER, List.of("xroad-registration-officer")); + userRoleMappings.put(XROAD_SERVICE_ADMINISTRATOR, List.of("xroad-service-administrator")); + userRoleMappings.put(XROAD_SYSTEM_ADMINISTRATOR, List.of("xroad-system-administrator")); + userRoleMappings.put(XROAD_SECURITYSERVER_OBSERVER, List.of("xroad-securityserver-observer")); + + if (complementaryUserRoleMappings != null) { + complementaryUserRoleMappings.forEach((role, groups) -> userRoleMappings.merge(role, groups, + (a, b) -> Stream.concat(a.stream(), b.stream()).collect(toList()))); + } + + return userRoleMappings; + } }