1. Introduction

ConfigSeeder is configuration management tool, not only capable of managing configuration values at a central place, but also manage their validity based on environment, application version, dates or contexts.

ConfigSeeder consists of different components:

Table 1. ConfigSeeder Components
Name Purpose

ConfigSeeder Management

Core component responsible to manage all configurations and provide configuration value retrieve services

ConfigSeeder Value Provider

High availability configuration value replication

ConfigSeeder Client

Clients for different languages and technologies.

  • Java: Core, Spring, Eclipse MicroProfile Config

  • Go

ConfigSeeder Kubernetes Connector

Component distributing API keys, secrets and config maps from ConfigSeeder (under development)

ConfigSeeder OS Connector

Component distributing files and configurations to different hosts. Can run under linux or windows and can optionally run as a service. (under development)

2. Quickstart

2.1. Start ConfigSeeder Management

  1. Download ConfigSeeder H2 Instance from https://configseeder.com/download/#cs-management

  2. Start instance with java -jar configseeder-server-management-app-h2-<version>.jar, or if you want to have persistence with H2, pass the database folder as follow: java -jar configseeder-server-management-app-h2-<version>.jar --spring.datasource.url=jdbc:h2:file:~/config-server.db. (For further options and configuration consult chapter ConfigSeeder Management Configuration)

  3. Open your Browser on http://localhost:8080/

  4. For providing security, a OIDC capable identity server is required. By default the delivered H2 instance is connected to a Keycloak Instance from Oneo GmbH.

    Login with user tenantadmin and password tenantadmin

  5. Create your first environments by clicking on the Environment link in the navigation bar. Then click on Create and fill in the form your new environment. (e.g. DEV, TEST and PROD). The most relevant value is the Key field, which will be used in future configurations.

    It’s a good practice to have no security restriction on any environment beside production. Leave everything on DERIVED for now.

    Create an environment
  6. As next create a configuration group. Navigate to Configuration Groups in the navigation bar and as next click on Create. In the form fill in the fields, where the most important part is again the Key field. We encourage to select either version or date for manage different configurations and reduce complexity. In this example, Date is disabled.

    Create a configuration
  7. Now you are ready to add new configurations. Click on your newly created Configuration Group and add your configuration. Depending on the current license different value types are supported. Create a new configuration and fill in the fields as desired. If different values for the same configuration key need to be created, click on the duplicate icon.

  8. In order to fetch values for your applications, you first need to create an API Key, which will give an application access to your configurations. Navigate to API Key and create a new one by clicking on Create. Define to which Configuration Groups and Environments the new key should grant access to. We also suggest to enter a period of validity as short as possible but no longer than two years.

    Create API Key - Step 1

    After clicking on Save the generated API Key is shown. You should keep it at a save place.

    Create API Key - Step 2

2.2. Start ConfigSeeder Clients

Time to connect your application to ConfigSeeder. For most of the following clients the general Client Introduction should be read first. Following options to integrate ConfigSeeder exists:

  1. Directly connect your application to ConfigSeeder with native clients

  2. Indirectly using a ConfigSeeder Connector.

    1. Curl

    2. Kubernetes Connector

    3. OS Connector

ConfigSeederOecoSystem

3. Applications

3.1. ConfigSeeder Management

3.1.1. Requirements

  • Environment

    • Database (PostgreSQL >= 9.5, SQL Server >= 2012)

    • OIDC or OAuth2 compliant identity server (e.g. Keycloak, Okta)

  • System

    • minimal: 512MB; recommended: 2GB

    • minimal: 1 core; recommended: >= 2 cores

    • JRE 11 (We recommend Zulu)

  • Browser

    • Chrome >= 56.X.X, Firefox >= 52 and other modern browsers

3.1.2. Setup steps

3.1.2.1. Preparations

Prepare before configuring ConfigSeeder Management:

  1. Decide which database you want to use (Supported: PostgreSQL, Microsoft SQL Server, Oracle)

  2. Get your trial or full ConfigSeeder license (mail at license@configseeder.com, mention: company name, database, number of configuration groups, number of environments)

  3. Setup Database (connection url, username, password) (empty database, user with DDL privilege)

  4. Identity Provider (urls, client id and secret, available profiles and attributes). Make sure groups or roles are available either as part of the access token or user info endpoint attribute)

  5. Define your hostname on which the application should be available

  6. If desired prepare a SSL Certificate

  7. Download ConfigServer Management instance from https://configseeder.com/download/#cs-management (Java JARs or Docker Image). Please be aware that the selection of your database has to match your licence.

3.1.2.2. Configure

Configurations can be passed in three variants and even mixed. We suggest to split up configurations as follow:

  • Non sensitive values: in a file called application.yaml

  • Sensitive values as environment variables

Follow these steps for the installation:

  1. Create application.yaml file

  2. Prepare configurations for:

  3. Run ConfigSeeder Management

  4. Stop ConfigSeeder Management

  5. Cleanup configurations

    1. Remove setup profile spring.profiles.active=setup

    2. Remove sensitive values from the configuration and pass it as environment variables

  6. Start ConfigSeeder and save all configurations and scripts

3.1.2.3. General
Table 2. General Configuration
Key M. Description

configseeder.server.url

M

URL to ConfigSeeder, e.g. https://demo.configseeder.com.

3.1.2.4. Database

Independent of the configuration database you are using, the configuration options are the same:

Table 3. Database Configuration
Key M. Description

spring.datasource.url

M

JDBC URL to your database.

  • H2: jdbc:h2:file:~/<filename>;AUTO_RECONNECT=TRUE

  • PostgreSQL: jdbc:postgresql://<host>:5432/<database>

  • SQL Server: jdbc:sqlserver://<host>;database=<database>

spring.datasource.username

M

Username

spring.datasource.password

M

Password

3.1.2.5. SSL Certificates

By default the application runs on port 8080 without encryption. You can easily force SSL connection by providing a SSL certificate. Follow SSL Certificate if you don’t know how to create a certificate.

Table 4. SSL Configuration
Key M. Description

server.port

Port on which ConfigSeeder should be running. For SSL we suggest port 8443 or 443

security.require-ssl

Set to true if you want to enforce SSL

server.ssl.key-store-type

Your keystore format. Our example uses PKCS12

server.ssl.key-store

Path to your keystore. E.g. /etc/configseeder/keystore.p12

server.ssl.key-store-password

Password to your keystore. In our example: changeit

server.ssl.key-alias

If more than one certificate is defined, use the alias or number. In our example: configseeder

3.1.2.6. Encryption Keys

ConfigSeeder Management has three encryption keys which needs to be configured. We strongly recommend to set your own values.

Table 5. Encryption Configuration
Key M. Description

configseeder.server.security.configurationvalue.encryption.secret

M

Encryption key to be used to encrypt the configuration values. Generate using AesKeyGenerator

configseeder.server.security.jwt.key-id

Key identification (defaults to configseeder-jwt-id)

configseeder.server.security.jwt.private-key

M

Private key as a base64 encoded string. Generate using RsaKeyGenerator

configseeder.server.security.jwt.public-key

M

Public key as a base64 encoded string. Generate using RsaKeyGenerator

configseeder.server.security.jwt.allow-weak-key

O

Allows keys with less bits than 2048. Defaults to false.

3.1.2.7. Setup Authentication

There are two different ways how authentication can configured.

  1. OIDC / OAuth2 (preferred, cannot be deactivated)

  2. Pre-Authenticated (disabled by default)

All authentication mechanism can be activated at the same time.

3.1.2.7.1. OIDC / OAuth2 with an Identity Provider

It is possible to configure multiple OIDC / OAuth2 identity providers, although this is a rare case. While configuring you application, the <id> placeholder in the key can be any simple string (pattern [a-z]+).

Table 6. OIDC / Oauth2 Settings
Key M. Description

configseeder.server.security.oauth.redirect.onlyone

Set to true if you have only one IdentityProvider configured and you want to be immediately redirected to the login screen

spring.security.oauth2.client.registration.<id>.client-id

M

Identification of the application provided by the Identity Provider

spring.security.oauth2.client.registration.<id>.client-secret

M

Secret of the application provided by the Identity Provider

spring.security.oauth2.client.registration.<id>.authorization-grant-type

Defaults to: authorization_code

spring.security.oauth2.client.registration.<id>.redirect-uri

M

URL where user will be redirected after authentication to the identity provider. Default value: {baseUrl}/login/oauth2/code/{registrationId}

spring.security.oauth2.client.registration.<id>.scope

M

Additional scopes. E.g. openid

spring.security.oauth2.client.registration.<id>.clientName

M

Which name will be used to show on the login screen

spring.security.oauth2.client.provider.<id>.authorization-uri

M

URI where user should be redirected to log in

spring.security.oauth2.client.provider.<id>.token-uri

M

URI where a the system can retrieve a token

spring.security.oauth2.client.provider.<id>.user-info-uri

URI where additional user details can be retrieved from the Identity Provider. This is needed if names, locale, roles or other user attributes should be retrieved

spring.security.oauth2.client.provider.<id>.jwk-set-uri

M for OIDC

URI to verify the token signature

spring.security.oauth2.client.provider.<id>.user-name-attribute

M

Attribute which should be used to fetch the username. Defaults to: preferred_username

spring.security.oauth2.client.mapping.<id>.firstname_lastname

Only set this value, if there are no separate field for firstname and lastname. Define which field should be taken to retrieve it. Defaults to null.

spring.security.oauth2.client.mapping.<id>.username

Set the field, if the username is not available on username field. Often it’s also login.

spring.security.oauth2.client.mapping.<id>.avatar_url

Set the field, if the avatar is not available on avatar_url. Often it’s picture. Could also be a base64 encoded image.

spring.security.oauth2.client.mapping.<id>.firstname

Set the field, if the firstname is not available as firstname. Often: given_name

spring.security.oauth2.client.mapping.<id>.lastname

Set the field, if the lastname ist not available as lastname. Often: family_name

spring.security.oauth2.client.mapping.<id>.accessTokenRolePaths

Set the path where the roles should be extracted from the access token. Example: realm_access.roles. Separate by comma if needed.

spring.security.oauth2.client.mapping.<id>.userInfoRolePaths

Set the path where the roles should be extracted from the user info endpoint. Example: roles,application.roles. Separate by comma if needed.

spring.security.oauth2.client.default.<id>.staticRoles

Comma separated list of roles which should be assigned to all authenticated users. E.g. READER or DEVELOPER. For Role Mappings consider chapter TODO

See ConfigSeeder Management Examples for real world values.

3.1.2.7.2. Pre-Authenticated

ConfigSeeder can be configured to trust pre-authenticated users over headers. This can be used where users are authenticated on a WAF with IAM support (e.g. Airlock) or Apache which is handling the authentication.

Note that currently at least the username and email header needs to be served. If username is not served, pre-authentication step will be skipped.

For these scenarios following configuration options are available:

Table 7. Pre-Authenticated Settings
Key M. Description

configseeder.server.security.auth.header.enabled

Set to true if you want to enable it. Defaults to false.

configseeder.server.security.auth.header.username-header

Which header contains the username. Defaults to: x-auth-username.

configseeder.server.security.auth.header.email-header

Header containing the e-mail. Defaults to x-auth-email.

configseeder.server.security.auth.header.firstname-header

Header containing the firstname. Defaults to x-auth-firstname.

configseeder.server.security.auth.header.lastname-header

Header containing the lastname. Defaults to x-auth-lastname.

configseeder.server.security.auth.header.name-header

Header containing the name. Defaults to x-auth-name.

configseeder.server.security.auth.header.roles-header

Header containing the roles. Defaults to x-auth-roles.

configseeder.server.security.auth.header.default-roles

Default roles if the header does not delivery any role. Defaults to empty.

configseeder.server.security.auth.header.ip-restriction

Comma separated IPs, from where the server shall accept header authentication. Only active if at least one ip is entered.

If pre-authentication works, a call to user’s endpoint should return a 200:

curl -X GET "https://staging-h2-cs.oneo.cloud/internal/user" -H "x-auth-username: john.doe" -H "x-auth-email: john.doe@mail.com" -H "accept: */*"
3.1.2.8. Configure Access Restrictions

ConfigSeeder distinguishes between functional permissions and data-based permissions. The functional permissions are grouped into six predefined function roles. The data-based permissions are defined at runtime by the configuration of the individual objects. These configurations enable a fine-granular authorization assignment, which can vary greatly depending on the intended use and can even become very complex. At this point, it is important to know which groups of people should have which authorizations.

In most of the cases it makes sense to apply at the beginning configurations based on one of the most matching scenario listed in chapter Access Scenarios and apply the documented configurations on the Role Mapping Configuration Page (https://<configseeder>/ui/role-management).

3.1.2.9. Other configurations

Here you will find all non-topic related configurations.

Table 8. Mail Settings
Key M. Description

spring.mail.host

Notification

SMTP Server Hostname

spring.mail.port

Notification

SMTP Server Port

spring.mail.username

Notification

SMTP Server Login (if needed)

spring.mail.password

Notification

SMTP Server Password (if needed)

spring.mail.properties.mail.smtp.auth

Notification

SMTP Authentication. Values: true or false

spring.mail.properties.mail.smtp.starttls.enable

Notification

SMTP Enable StartTLS. Values: true or false
Table 9. API Key
Key M. Description

configseeder.server.api-key.max-validity

Number of maximum days an API Key can be valid. Defaults to 2 years ( = 730 days)
Table 10. UI Settings
Key M. Description

server.port

Port on which the server listens (Defaults to 8080)

configseeder.server.ui.urls.logo

URL to the logo on the left top corner

configseeder.server.ui.calendar.firstDayOfWeek

Calendar: First day of week. 0 = Sunday, 1 = Monday

configseeder.server.ui.global-config.languages

Available languages in the UI splitted by comma (Defaults to all supported languages = de,en)

configseeder.server.ui.global-config.logging.client-log-level

Log level on client side. Defaults to INFO

configseeder.server.ui.global-config.logging.client-server-log-level

Which client logs are shown on server side. Defaults to INFO
Table 11. API key expiration notification Settings
Key M. Description

configseeder.server.notification.apiKeyExpiration.cron

Cron expression to set up scheduling of expiration notifications job (Defaults to 2 A.M.). CronSequenceGenerator

configseeder.server.notification.apiKeyExpiration.sender

M

Sender of expiration notification

configseeder.server.notification.apiKeyExpiration.notificationDaysBeforeExpiry

Number of days before expiration date to send notification (Defaults to 14 days)

configseeder.server.notification.apiKeyExpiration.recipientEmails

List of email addresses where to send expiration notification separated by ,

configseeder.server.notification.apiKeyExpiration.notifyCreator

Flag to notify creator of the api key. Defaults to true
Table 12. Miscellaneous
Key M. Description

configseeder.server.action-log.enabled

If action logs should be collected. Default: true.

configseeder.server.action-log.log-reads

If action logs should log read operations. Default: true.

configseeder.server.action-log.log-writes

If action logs should log write operations. Default: true.

configseeder.server.action-log.lazy-persistence.enabled

If action log should be persisted lazily. Defaults to true.

configseeder.server.action-log.lazy-persistence.initial-delay

Initial delay for the first persistence operation in milliseconds. Defaults to 10000.

configseeder.server.action-log.lazy-persistence.fixed-rate

How often lazy persistence should be performed in milliseconds. Defaults to 10000.

configseeder.server.action-log.cron

At which time action logs should be cleaned up. Defaults to 0 0 3 ? * *.

configseeder.server.action-log.cleanup-after-days

Cleanup action logs older than x days. Defaults to 62.

configseeder.server.setup.login.credential-needed

If a credential needs to be passed when running in setup mode and creating a setup user. Defaults to true

configseeder.server.setup.login.credential

A default credential, if not a new credential shall be generated on each startup. Default: `` (empty ⇒ new one will be generated).
Table 13. Miscellaneous (from Spring)
Key Description

spring.servlet.multipart.max-file-size

Maximum upload file size

spring.servlet.multipart.max-request-size

Maximul request size

3.1.3. Run ConfigSeeder Management

3.1.3.1. Java

Pass sensitive value as environment variables:

export SPRING_DATASOURCE_PASSWORD="..."
export SERVER_SSL_KEYSTOREPASSWORD="..."
export CONFIGSEEDER_SERVER_SECURITY_JWT_PRIVATEKEY="..."
export CONFIGSEEDER_SERVER_SECURITY_JWT_PUBLICKEY="..."
export CONFIGSEEDER_SERVER_SECURITY_CONFIGURATIONVALUE_ENCRYPTION_SECRET="..."
java -Xms512M -Xmx512M -jar configseeder-server-management-app-<db>-<version>.jar

You may pass them also as parameters, but then it would be visible with ps / cat /proc/<pid>/cmdline:

java -Xms512M -Xmx512M -jar configseeder-server-management-app-<db>-<version>.jar \
  --spring.datasource.password=... \
  --server.ssl.key-store-password=... \
  --configseeder.server.security.jwt.private-key=... \
  --configseeder.server.security.jwt.public-key=... \
  --configseeder.server.security.configurationvalue.encryption.secret=...

Add --spring.config.location=/<path>/application.yaml to the command line if the configuration file is not in the same place as the jar-file.

3.1.3.2. Docker
docker run -d -it \
  -v `pwd`/application.yaml:/application.yaml \
  -e JAVA_OPTS="-Xms512M -Xmx512M" \
  --name configseeder \
  -p 8080:8080 \
  configseeder/management-h2:<version>-alpine

You may pass secrets as environment parameters, but then it would be visible with ps / cat /proc/<pid>/cmdline. We strongly disadvise.

3.1.4. Access Restriction

As mentioned in chapter Configure Access Restrictions ConfigSeeder distinguishes between functional permissions and data-based permissions. Let’s dive more into how these permissions work.

3.1.4.1. Data Based Permissions

Based on the security configurations of the Tenant, Environment and Configuration Group objects, ConfigSeeder generates the required data authorization roles. It is important to understand that data access configurations are inherited to dependent entities.

SecurityEntityModel

Each of the secured entities can be configured at least with following two attributes:

  • Secured Type: Restricts access based on the type of operation:

    • DERIVED: Same rule applies for the most restrictive parent object, access is granted

    • W_ROLE: Read access is drived from the parent object. Dedicated role needed for write operations.

    • RW_ROLE: One dedicated role is needed to perform read and write operations.

    • SEPARATE_RW_ROLES: Separate roles for read and write are needed.

  • Secured Role Infix: Role Name Fraction used.

The generated data roles are additionally influenced by the configurations listed bellow. We suggest to keep them as they are:

Table 14. Data Based Permission Configuration
Key M. Description

configseeder.server.security.roles.prefix

Prefix for all data access roles. Defaults to cs

configseeder.server.security.roles.infixSeparator

Separator character. Defaults to _

configseeder.server.security.roles.readSuffix

Suffix for all read-specific data roles. Defaults to r

configseeder.server.security.roles.writeSuffix

Suffix for all write-specific data roles. Defaults to w

configseeder.server.security.roles.securedInfix

Infix for all secured marked configuration values. Defaults to sec

configseeder.server.security.roles.allInfix

Role suffix for having privileges for all sub entities. Defaults to all

Based on the configurations and the security settings of each object a role is generated in following format:

ROLE_PATTERN = ${configseeder.server.security.roles.prefix} + "_" + OBJECT_PATH + SECURED + SUFFIX (1)
OBJECT_PATH = [ TENANT_INFIX + "_" | ] + [ CONFIGURATION_GROUP_INFIX + "_" | ] + [ ENVIRONMENT_INFIX + "_" | ] (2)
SECURED = [ ${configseeder.server.security.roles.securedInfix} + "_" | ] (3)
SUFFIX = [ ${configseeder.server.security.roles.readSuffix} | ${configseeder.server.security.roles.writeSuffix} | ] (4)
1 The minimal ROLE_PATTERN path is ${configseeder.server.security.roles.prefix}.
2 If securedType != DERIVED, then the configured infix of the object is used
3 If the configuration value is marked with secured = true, then the infix will be added
4 If at least one object has securedType == SEPARATE_RW_ROLES and access mode is READ then the ${configseeder.server.security.roles.readSuffix} is used.
If at least one object has securedType == W_ROLE || securedType == SEPARATE_RW_ROLES and access mode is W_ROLE then the ${configseeder.server.security.roles.writeSuffix} is used.
The ${configseeder.server.security.roles.allInfix} (defaults to all) gives access to the entity and sub entities.

You can find in the appendix the chapter ConfigSeeder Management - Security with more details.

3.1.4.2. Function Based Permissions

ConfigSeeder offers by default a set of function roles.

Table 15. Default Function Role Privilege Mapping
Function Role Description

SUPERADMIN

Administrator with all permissions. Should only be assigned to a limited number of users.

LICENSEMANAGER

License manager can only change the license.

TENANTADMIN

Sub-administrator, who is responsible for the administration of ConfigSeeder, but without license management. Has authorization to change environments and configuration groups.

APPLICATIONMANAGER

Can modify configuration groups and values, but no environments.

DEVELOPER

Can modify configurations, but no environments or configuration groups.

READER

Read only access.

OPERATOR

Operator only access

For more details which privileges they contain, see Function Role Privilege Assignment.

3.1.5. Access Scenarios

3.1.5.1. One Team with access to everything

Scenario: ConfigSeeder is just used by one team which has access to everything.

Table 16. Object Configuration (One Team)
Object Security Config

Configuration Group

  • Secured Type: inherit

  • Secured Values: inherit

Environment

  • Secured Type: inherit

Configuration Value

  • Secured: false (default)

Table 17. Role Mapping (One Team)
Name Identity Provider Role Function Role Data Role

Admin

Admin

  • SUPERADMIN

  • cs_all

Devops

User

  • APPLICATIONMANAGER

  • DEVELOPER

  • cs_all

3.1.5.2. DEVOPS by Application

Scenario: Administrator Role and independent DEVOPS Team with full access.

Table 18. Object Configuration (DEVOPS Teams)
Object Security Config

Configuration Group

  • Secured Type: read or write

  • Secured Role Infix: devopsteam1

  • Secured Values: inherit

Environment

  • Secured Type: inherit

Configuration Value

  • Secured: false (default)

Table 19. Role Mapping (DEVOPS Teams)
Name Identity Provider Role Function Role Data Role

Admin

Admin

  • SUPERADMIN

  • cs_all

DevOpsTeam1

DevOpsTeam1

  • APPLICATIONMANAGER

  • DEVELOPER

  • cs

  • cs_devopsteam1_all

3.1.5.3. Application Management and Suborganizations

Scenario: An application management team is responsible for all productive installations. Each suborganization has couple of leaders, which are also able to edit production values. Developers can only see production values, which are not secured. We assume that production environment is called prod.

Table 20. Object Configuration (Org Teams)
Object Security Config

Configuration Group

  • Secured Type: read or write

  • Secured Role Infix: org1

  • Secured Values: read or write

Environment

All exl. PROD:

  • Secured Type: inherit

PROD environment:

  • Secured Type: read and write

  • Secured Role Infix: prod

Configuration Value

  • Secured: true (if secrets should not be shown to the developers)

Table 21. Role Mapping (Org Teams)
Name Identity Provider Role Function Role Data Role

Admin

Admin

  • SUPERADMIN

  • cs_all

Leader Org1

Org1Lead

  • APPLICATIONMANAGER

  • DEVELOPER

  • cs

  • cs_org1_all

Developer Org1

Org1Developer

  • DEVELOPER

  • cs

  • cs_org1

  • cs_org1_pp_r

3.1.6. Example ConfigSeeder Management Configurations

GitHub OAuth2 Configuration (Spring Boot Hint: CommonOAuth2Provider)
spring.security.oauth2.client.provider.github.authorization-uri=              https://github.com/login/oauth/authorize
spring.security.oauth2.client.provider.github.token-uri=                      https://github.com/login/oauth/access_token
spring.security.oauth2.client.provider.github.user-info-uri=                  https://api.github.com/user
spring.security.oauth2.client.registration.github.client-id=                  0a3f5a2be63c98cb2741
spring.security.oauth2.client.registration.github.client-secret=              1d031b6bf2c5865160319b027636cd88d290b9eb
spring.security.oauth2.client.registration.github.authorization-grant-type=   authorization_code
spring.security.oauth2.client.registration.github.redirect-uri=               {baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.github.scope=                      read:user,user:email
spring.security.oauth2.client.registration.github.clientName=                 GitHub
spring.security.oauth2.client.mapping.github.firstname_lastname=              name
spring.security.oauth2.client.mapping.github.username=                        login
spring.security.oauth2.client.default.github.staticRoles=                     TENANT_ADMIN,DEVELOPER,cs  (1)
1 As GitHub brings no role with, we need to statically assign some roles to each user
Keycloak OICD Configuration
spring.security.oauth2.client.provider.keycloak.authorization-uri=            https://keycloak.oneo.ch/auth/realms/config-server/protocol/openid-connect/auth
spring.security.oauth2.client.provider.keycloak.token-uri=                    https://keycloak.oneo.ch/auth/realms/config-server/protocol/openid-connect/token
spring.security.oauth2.client.provider.keycloak.user-info-uri=                https://keycloak.oneo.ch/auth/realms/config-server/protocol/openid-connect/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri=                  https://keycloak.oneo.ch/auth/realms/config-server/protocol/openid-connect/certs
spring.security.oauth2.client.provider.keycloak.user-name-attribute=          preferred_username
spring.security.oauth2.client.registration.keycloak.client-id=                oneo-config-server
spring.security.oauth2.client.registration.keycloak.client-secret=            oneo-config-server
spring.security.oauth2.client.registration.keycloak.authorization-grant-type= authorization_code
spring.security.oauth2.client.registration.keycloak.redirect-uri=             {baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.keycloak.scope=                    openid,profile
spring.security.oauth2.client.registration.keycloak.clientName=               Keycloak
spring.security.oauth2.client.mapping.keycloak.firstname=                     given_name
spring.security.oauth2.client.mapping.keycloak.lastname=                      family_name
spring.security.oauth2.client.mapping.keycloak.username=                      preferred_username
spring.security.oauth2.client.mapping.keycloak.avatar_url=                    avatar
spring.security.oauth2.client.mapping.keycloak.accessTokenRolePaths=          realm_access.roles

3.1.7. Tooling

3.1.7.1. Health Details

Reports about health status of ConfigSeeder can be retrieved on:

$ curl 'https://<your-host-name>/actuator/health' -i -X GET
3.1.7.2. Access Metrics

Get access to ConfigSeeder metrics, especially number of requests on the configuration endpoint:

curl 'https://<your-host-name>/actuator/metrics/configseeder.external.timer/' -i -X GET

Access for specific tags (e.g. environment PROD):

curl 'https://<your-host-name>/actuator/metrics/configseeder.external.timer/?tag=environment%3APROD' -i -X GET
3.1.7.3. Change Log Levels

As ConfigSeeder is a SpringBoot Application, log levels can be changed at runtime. For this use:

$ curl 'https://<your-host-name>/actuator/loggers/com.configseeder' -i -X POST \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{"configuredLevel":"debug"}'

Clear your settings to your default:

$ curl 'http://<your-host-name>/actuator/loggers/com.example' -i -X POST \
    -H 'Authorization: Bearer <token>' \
    -H 'Content-Type: application/json' \
    -d '{}'

3.1.8. Trouble Shooting

3.1.8.1. OIDC

If you encounter any issue for setup OIDC, change loglevel for security to TRACE. This can be performed with:

logging.level.com.configseeder.infrastructure.security=TRACE
  • [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: I/O error on POST request for "<host>": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

    ⇒ The SSL certificate is not known to ConfigSeeder. Probably it’s a self-signed certificate or the root ca is not known to ConfigSeeder.

  • OAuth2AuthenticationException: [invalid_token_response]: Operation timed out (Connection timed out)

    ⇒ Most probably the connection to the OIDC system is blocked by firewall or missing proxy configuration (e.g. -Dhttps.proxyHost= and -Dhttps.proxyPort= and -Dhttps.proxySet=true)

  • org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_client]

    ⇒ Missing configuration for client-id or client-secret

3.2. Value Provider (HA)

3.2.1. Requirements

  • System (per Value Provider instance)

    • minimal: 512MB; recommended: 2GB

    • minimal: 1 core; recommended: >= 2 cores

    • JRE 11 (We recommend Zulu)

  • ETCD installation (if used with HA Data store)

  • LoadBalancer (if multiple Value Providers will be used)

3.2.2. Setup Steps

3.2.2.1. Preparations

Prepare before configuring ConfigSeeder ValueProvider:

  1. Decide if a HA store should be used (no HA store → each ValueProvider synchronizes on it’s own, ETCD. Please be advised, without an ETCD cluster, HA can not be achieved for some scenarios.)

  2. ConfigSeeder Management is running

3.2.2.2. Configure

Configurations can be passed in three variants and even mixed. We suggest to split up configurations as follow:

  • Non sensitive values: in a file called application.yaml

  • Sensitive values as environment variables

Follow these steps for the installation:

3.2.2.3. SSL Certificates

By default the application runs on port 8080 without encryption. You can easily force SSL connection by providing a SSL certificate. Follow SSL Certificate if you don’t know how to create a certificate.

Table 22. SSL Configuration
Key M. Description

server.port

Port on which ConfigSeeder should be running. For SSL we suggest port 8443 or 443

security.require-ssl

Set to true if you want to enforce SSL

server.ssl.key-store-type

Your keystore format. Our example uses PKCS12

server.ssl.key-store

Path to your keystore. E.g. /etc/configseeder/keystore.p12

server.ssl.key-store-password

Password to your keystore. In our example: changeit

server.ssl.key-alias

If more than one certificate is defined, use the alias or number. In our example: configseeder

3.2.2.4. Encryption Keys

ConfigSeeder ValueProvider should use the same encryption keys as the management instance.

Table 23. Encryption Configuration
Key M. Description

configseeder.server.security.jwt.private-key

M

Private key as a base64 encoded string - same as ConfigSeeder Management

configseeder.server.security.jwt.public-key

M

Public key as a base64 encoded string - same as ConfigSeeder Management

configseeder.server.security.jwt.key-id

Key identification (defaults to configseeder-jwt-id) - same as ConfigSeeder Management

configseeder.server.security.configurationvalue.encryption.secret

M

Encryption key to be used to encrypt the configuration values - same as ConfigSeeder Management

3.2.2.5. Synchronisation

In order that the ConfigSeeder ValueProvider is capable of retrieving the values, a synchronization API Key needs to be created in ConfigSeeder Management.

Ensure that the privilege Synchronisation is checked (a synchronisation API key can only be generated by SuperAdmin).

Table 24. API Key Configuration
Key M. Description

configseeder.server.ha.synchronization.enabled

M

Set to true if it’s the master instance. Defaults to false.

  • Must be set to true exactly once per ETCD Cluster.

  • Must be set to true for every ValueProvider in Mode In Memory

configseeder.server.ha.synchronization.host

M

ConfigSeeder Management host name

configseeder.server.ha.synchronization.apiKey

M

API Key from ConfigSeeder Management, which should be used for synchronisation

configseeder.server.ha.synchronization.initialDelay

Defaults to 5000 msec

configseeder.server.ha.synchronization.fixedDelay

Defaults to 5000 msec

configseeder.server.ha.synchronization.connectTimeout

Connect timeout. Defaults to 200 msec

configseeder.server.ha.synchronization.readTimeout

Read timeout. Defaults to 5000 msec

configseeder.server.ha.synchronization.filter.tenantKeys

Filter to include only specific tenants (tenant keys, separated by ,)

configseeder.server.ha.synchronization.filter.environmentKeys

Filter to include only specific environments (environment keys, separated by ,)

configseeder.server.ha.synchronization.filter.configurationGroupKeys

Filter to include only specific configuration groups (configuration group keys, separated by ,)

3.2.2.6. In Memory

Setup in memory instance

Table 25. In Memory Configuration
Key M. Description

spring.profiles.active

M

Set value to inmemory

Be advised that Value Providers which run In Memory in combination with a LoadBalancer can protect against some failure scenarios. However it is not possible to start up a Value Provider without a HA Datastore (ETCD) when ConfigSeeder Management is not available.

3.2.2.7. ETCD

Setup ETCD instance (persistence and synchronize over ETCD). Master instance will update all slaves.

Table 26. ETCD Configuration
Key M. Description

configseeder.server.ha.etcd.uris

M

Comma separated URIs to ETCD instances

configseeder.server.ha.etcd.timeout

Defaults to 5000 msec

configseeder.server.ha.etcd.username

Needed if authentication is enabled

configseeder.server.ha.etcd.password

Needed if authentication is enabled

configseeder.server.ha.etcd.pathPrefix

Where configseeder values are stored. Defaults to /configseeder

3.2.3. Run ConfigSeeder ValueProvider

3.2.3.1. Java
java -Xms512M -Xmx512M -jar configseeder-server-ha-configuration-provider-<db>-<version>.jar \
  --configseeder.server.ha.etcd.password=... \
  --server.ssl.key-store-password=... \
  --configseeder.server.security.jwt.private-key=... \
  --configseeder.server.security.jwt.public-key=... \
  --configseeder.server.security.configurationvalue.encryption.secret=...

Add --spring.config.location=/<path>/application.yaml to the command line if the configuration file is not in the same place as the jar-file.

3.2.3.2. Docker
docker run -d -it \
  --mount type=bind,source="$(pwd)"/application.yaml,target=/application.yaml \
  -e JAVA_OPTS="-Xms512M -Xmx512M"
  -e CONFIGSEEDER_SERVER_HA_ETCD_PASSWORD=... \
  -e SERVER_SSL_KEYSTOREPASSWORD=... \
  -e CONFIGSEEDER_SERVER_SECURITY_JWT_PRIVATEKEY=... \
  -e CONFIGSEEDER_SERVER_SECURITY_JWT_PUBLICKEY=... \
  -e CONFIGSEEDER_SERVER_SECURITY_CONFIGURATIONVALUE_ENCRYPTION_SECRET=... \
  --name configseeder \
  -p 8080:8080 \
  configseeder/ha-configuration-provider:<version>

3.3. ConfigSeeder Clients

3.3.1. Common Setup Steps

All configuration parameters can be passed using a YAML file and be overwritten by system environment variables or start parameters. You can create your configuration manually by following this manual or generate it in ConfigSeeder Management by clicking on the Export button in the configuration management screen.

It’s strongly suggested to pass all parameters that may be different per environment as a parameters and not part of your artifact.
Although it’s possible to have API Keys part of a configuration file, we discourage your saving ApiKey in your file and persist it into your repository.
  1. Create a configseeder.yaml file containing all static configuration without any sensitive values (e.g. like API-Keys), dynamic values (like Hostname or Environment).

    At least configure the environment and one configuration. A minimal configuration looks like this:

    configseeder.yaml
    configseeder:
      client:
        environmentKey: "DEV"
        configurationGroupKeys: "demo"

    It is also possible to configure more settings. A full blown configuration example can be found bellow. Note that . For full reference of all configurations, please refer chapter All available configuration values.

    configseeder.yaml
    configseeder:
      client:
        tenantKey: "default"
        environmentKey: "DEV"
        version: "2.0.24"
        context: "local"
        configurations:
        - key: "group1"
          selectionMode: "LATEST"
        - key: "group2"
          version: 2
        initializationMode: "EAGER_ASYNC"
        refreshMode: "LAZY"
        refreshCycle: 15000
        retryWaitTime: 1000
        maxRetries: 3
        connectionTimeout: 500
        readTimeout: 2000
        serverUrl: "https://config-server-demo.oneo.ch"
        apiKey: "eyJ....KEUg0o"

The keys configurations as well as configurationGroupKeys specifies which configuration group(s) should be loaded. If the configuration is done with configurationGroupKeys, the client will always load the latest version. When using configurations it can be specified per configuraiton group which version should be retrieved.

  1. Pass all other mandatory parameters (e.g. dynamic or sensitive data like config server host name and API Key) as java parameters or environment variables.

    Example for Java Clients
    java -Dconfigseeder.client.serverUrl=https://demo-config-server.oneo.cloud/ -Dconfig-client.api-key=eyJ....KEUg0o -jar <yourapp>.jar

3.3.2. Best practices

  • Always pass API-Key as Java parameter or environment variable.

  • On non-dev instances pass also hostname as parameter instead of fixed configured in configseeder.yaml

  • If you pass the version of your application as config server argument, make sure configseeder.yaml get’s filtered and use variable @project.version@ in the file. Example:

    configseeder:
      client:
        version: "@project.version@"
  • If your application will not work with new values from ConfigSeeder received at runtime, just configure the refreshMode with NEVER

3.3.3. All available configurations

Configs are prefixed with configseeder.client and environment configs with CONFIGSEEDER_CLIENT. Example: configseeder.client.serverUrl and CONFIGSEEDER_CLIENT_SERVERURL.
Table 27. ConfigServerClient Paramenters
Config Environment Variable Mand./Opt Description

..serverUrl

__SERVERURL

M

URL of the config server

..apiKey

__APIKEY

M

API Key

..tenantKey

__TENANTKEY

O

Key of the tenant

..environmentKey

__ENVIRONMENTKEY

M

Key of the environment

..version

__VERSION

O

Filter: Version of the application

..context

__CONTEXT

O

Filter: Context

..dateTime

__DATETIME

O

Filter: Date Time (Default: now())

..configurations.<n>.key

__CONFIGURATIONS_<n>_KEY

(M)

Name of the configuration group

..configurations.<n>.selectionMode

__CONFIGURATIONS_<n>_SELECTIONMODE

O

If there are multiple versions: LATEST, LATEST_RELEASED

..configurations.<n>.version

__CONFIGURATIONS_<n>_VERSION

O

Instead of selectionMode choose explicitly a version

..configurationGroupKeys

__CONFIGURATION_GROUP_KEYS

(M)

This key can be used instead of the configurations-Keys. Used selection mode is LATEST

..initializationMode

__INITIALIZATIONMODE

O

How all values should be initialized. Following options exists:

- EAGER: Loads values on startup of the application synchronously (blocking)

- EAGER_ASYNC: Loads values on startup with a separate thread (async) (default)

- LAZY: Loads values only on first request (blocking)

..refreshMode

__REFRESHMODE

O

When the values shall be refreshed:

- TIMER: Loads the values regularly (default)

- MANUAL: Application needs to call ConfigClientProvider.refreshValues() manually

- LAZY: Only on request

- NEVER: Never loads and updates the values. Same as MANUAL.

..refreshCycle

__REFRESHCYCLE

O

How often values get refreshed (in milliseconds, default: 15000)

..retryWaitTime

__RETRY_WAIT_TIME

O

How many milliseconds should be waited before a new request is performed (default: 500)

..maxRetries

__MAX_RETRIES

O

How many times connection retry should be performed (default: 3)

..connectionTimeout

__CONNECTIONTIMEOUT

O

How long the client shall wait for initial connection (default: 500)

..readTimeout

__READTIMEOUT

O

How long an answer from the server shall take max (default: 2000)

..disable

__DISABLE

O

Disable configseeder client with true

..recoveryFile

__RECOVERYFILE

O

If defined the given file will be created to store the latest answer from Config Seeder Management. On the next startup, if Config Seeder Management is not reachable, file contents will be used. If it’s not a file but a folder, a dedicated .configseeder-recovery.json file will be created to store the contents.

3.3.4. Curl

Any configuration can be downloaded with curl and an appropriate API Key from ConfigSeeder. The curl command can be generated in ConfigSeeder Management on the Export view.

For completeness here an example curl statement (it is also possible to retrieve configuration data depending on date or context):

curl -X POST -H "Content-type: application/json" -H "Accept: text/x-java-properties" -d '{
  "tenantKey": "default",
  "environmentKey": "DEV",
  "version": "15.1.0",
  "configurations": [
    {
      "configurationGroupKey": "group1"
    }
  ]
}' 'https://<server>/external/configurations?download=false'

Following accept types are supported:

  • text/x-java-properties (Java Properties Files)

  • text/yaml (YAML Files)

  • application/x-ini (Windows INI Files)

  • text/csv (CSV Files)

  • application/json (Fully fledged detail)

3.3.5. Eclipse Micro Profile Config

3.3.5.1. Requirements
  • Application Server supporting Eclipse Microprofile Config >= 1.1.

  • Download matching version of config-client-microprofile-config-*.jar from our maven repository: https://maven.configseeder.com/

3.3.5.2. Configuration

Add config-client-microprofile-config-*.jar as dependency:

pom.xml
<dependency>
  <groupId>com.configserver.client</groupId>
  <artifactId>configseeder-client-microprofile-config</artifactId>
  <version>${configseeder.version}</version>
</dependency>
3.3.5.3. Usage of the configuration values

Following examples show how you can inject a value at startup. Note that this value will never change anymore, although the may have been altered on the ConfigSeeder Management.

StaticExample.java
// String value
@Inject
@ConfigProperty(name = "BAR", defaultValue = "default value if BAR is not provided")
String bar;

// Boolean value
@Inject
@ConfigProperty(name = "BOOL_PROP", defaultValue = "no")
boolean boolProp;

If you want to retrieve your values at runtime, just inject Config and retrieve the value each time.

DynamicExample.java
@Inject
Config config;

@Inject
@ConfigProperty(name = "server.host.name")
javax.inject.Provider<String> hostName;

private void doSomeOperation() {
   // dynamic optional value
   Optional<String> proxy = config.getOptionalValue("mail.proxy", String.class);

   // dynamic value
   String hostname = config.getValue("mail.host", String.class);

   // dynamic with provider
   String resolvedHostName = hostName.get();

   ...
}
3.3.5.3.1. Best practices
  • Development:

    1. Create a file /src/main/resources-dev/local.properties (or *.yaml) with all properties which are expected. Reason: File has higher priority than the config server and is not part of the default build.

    2. Include folder /src/main/resources-dev/ only with development profile, so that it’s not part of the regular build artifact.

      With maven:

      <profiles>
        <profile>
          <id>dev</id>
          <build>
            <resources>
              <resource>
                <directory>src/main/resources-dev</directory>
              </resource>
            </resources>
          </build>
        </profile>
      </profiles>
3.3.5.3.2. Priority

ConfigSeeder Config Source has currently a priority of 200. If this should be configurable, create an issue on GitLab.

3.3.6. Spring

3.3.6.1. Requirements
3.3.6.2. Configuration

Add config-client-spring-*.jar as dependency:

pom.xml
<dependency>
  <groupId>com.configseeder.client</groupId>
  <artifactId>configseeder-client-spring</artifactId>
  <version>${configseeder.version}</version>
</dependency>
3.3.6.3. Usage of the configuration values

Following examples show how you can inject a value at startup. Note that this value will never change anymore, although the may have been altered on the config server management instance.

StaticExample.java
// String value
@Value("${bar.key:my-default}")
String bar;

// Boolean value
@Value("${bar.a-boolean-value}")
boolean boolProp;

If you want to retrieve your values at runtime, just inject Environment and retrieve the value each time.

DynamicExample.java
@Autowired
Environment environment;

private void doSomeOperation() {
   // dynamic value with default
   String proxy = environment.getProperty("mail.proxy", "defaul-proxy.oneo.ch");

   // dynamic value with boolean datatype
   Boolean featureEnabled = environment.getProperty("mail.feature.enabled", Boolean.class);

   ...
}
3.3.6.4. Best practices
  • Development:

    1. Create a file /src/main/resources-dev/application.properties (or *.yaml) with all properties which are expected. Reason: File has higher priority than the config server and is not part of the default build.

    2. Include folder /src/main/resources-dev/ only with development profile, so that it’s not part of the regular build artifact.

      With maven:

      <profiles>
        <profile>
          <id>dev</id>
          <build>
            <resources>
              <resource>
                <directory>src/main/resources-dev</directory>
              </resource>
            </resources>
          </build>
        </profile>
      </profiles>
3.3.6.5. Priority

Values from ConfigSeeder have always the least priority. Every local configuration will win.

3.3.7. Plain Java

3.3.7.1. Requirements
3.3.7.2. Configuration
  1. Add config-client-core-*.jar as dependency:

    pom.xml
    <dependency>
      <groupId>com.configseeder.client</groupId>
      <artifactId>configseeder-client-core</artifactId>
      <version>${configseeder.version}</version>
    </dependency>
  2. If desired you can create a /application.properties file which could contain properties not available on the config server or have precendence over the config server.

3.3.7.3. Usage
  • Core ships a Config class, which can be used to easily retrieve values using default settings:

    SimpleConfigExample.java
    String stringValue = Config.getString("any.existing.string");
    String fallbackValue = Config.getString("any.notexisting.string", "fallback");
    Optional<String> optionalValue = Config.getOptionalString("any.optional.string");
    
    Long longValue = Config.getLong("any.existing.long");
    Long fallbackLongValue = Config.getLong("any.notexisting.long", 42L);
    Optional<Long> optionalLong = Config.getOptionalLong("any.optional.long");
    
    Boolean booleanValue = Config.getBoolean("any.existing.boolean");
    boolean fallbackBoolean = Config.getBoolean("any.notexisting.boolean", true);
    Optional<Boolean> optionalBoolean = Config.getBoolean("any.optional.boolean");
  • If you want to configure the ConfigSeederClient yourself or using a more dynamic way, use:

    CustomizedConfigSeederClient.java
    ConfigSeederClientConfiguration configSeederClientConfiguration = ConfigSeederClientConfiguration.fromYaml("/configseeder.yaml");
    ConfigSeederClient configSeederClient = new ConfigSeederClient(configSeederClientConfiguration);
    ...
    String stringValue = configSeederClient.getString("foo.bar");

    Make sure you create only one ConfigSeederClient instance.

3.3.8. Maven

3.3.8.1. Requirements
3.3.8.2. Configuration

Add following configuration to your pom.xml:

pom.xml
<build>
  <resources>
    <resource>
      <directory>src/main/resources</directory>
      <includes>
        <include>filtered.properties</include>
      </includes>
      <filtering>true</filtering>
    </resource>
  </resources>
  <plugins>
    <plugin>
      <groupId>com.configseeder.client.maven</groupId>
      <artifactId>configseeder-client-maven-plugin</artifactId>
      <version>${project.version}</version>
      <goals>
         <goal>fetch</goal>
      </goals>
      <executions>
        <execution>
          <phase>initialize</phase>
          <goals>
            <goal>fetch</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <serverUrl>https://config-server-demo.oneo.ch</serverUrl>
        <apiKey>ey...Ug0o</apiKey>
        <tenant>demo</tenant>
         <environment>PROD</environment>
         <version>${project.version}</version>
         <configurationGroups>
           <configurationGroup>
             <key>cube</key>
           </configurationGroup>
         </configurationGroups>
       </configuration>
    </plugin>
  </plugins>
</build>
3.3.8.3. Usage
  • Use any property as replacement variable in your filtered files.

    1. Make sure files are filtered:

      pom.xml
      <resources>
        <resource>
          <directory>src/main/resources</directory>
          <includes>
            <include>filtered.properties</include>
          </includes>
          <filtering>true</filtering>
        </resource>
      </resources>
    2. Use the variables in your filtered files:

      filtered.properties
      foo.bar=@foo.bar@
  • Use any property in your pom.xml as variable:

    pom.xml
    <dependencies>
      <dependency>
        <groupId>org.apache.maven</groupId>
        <artifactId>maven-plugin-api</artifactId>
        <version>${my.configseeder.maven-plugin-version}</version>
      </dependency>
    </dependency>

3.3.9. Template

ConfigSeeder offers the functionality to replace all variables in a file using the replacement mechanism. ConfigSeeder analyses the file and will try to resolve all variables. You can choose how a variable is built. For our Demo case we assume that a variable is built like this: ${variable.key}. ConfigSeeder will look for the value stored on key variable.key.

The same as for all other configurations, ConfigSeeder needs to know following parameters:

  • On which configuration groups the request shall be performed.

  • Restriction filters like environment, version, date or/and context

  • API Key in order to authenticate.

  • The prefix and postfix of the variables (${ and }) are encoded into the url, see attributes variablePrefix and variablePostfix.

A full fledged request may look like this:

curl -X POST \
  --header 'Authorization: Bearer <apiKey>' \
  --header 'Content-Type: multipart/form-data' \
  --header 'Accept: application/octet-stream' \
  --form "configuration=@/tmp/request.json;type=application/json" \
  --form "template=@/tmp/original.sh" \
  --output /tmp/replaced.sh \
  'https://demo.configseeder.com/external/configurations/replace?variablePrefix=%24%7B&variablePostfix=%7D'

Where the files look like this:

request.json
{
  "configurations": [
    {
      "configurationGroupKey": "MyConfigurationGroup",
      "selectionMode": "LATEST"
    }
  ],
  "dateTime": "2018-12-11T08:24:59.535Z",
  "environmentKey": "DEV",
  "version": "2.0.0-RC1"
  "context": "test"
}
original.sh
#!/bin/sh

VARIABLE="${variable.key}"

echo $VARIABLE;

This will result in a file like:

replaced.sh
#!/bin/sh

VARIABLE="Hello World"

echo $VARIABLE;

It is possible to optionally pass a file with additional variables as JSON:

variables.json
{
  "this.is.my.key": "this is a value",
  "another.key": "another value",
  "key1": "value1"
}

The curl command would look like this:

curl -X POST \
  --header 'Authorization: Bearer <apiKey>' \
  --header 'Content-Type: multipart/form-data' \
  --header 'Accept: application/octet-stream' \
  --form "configuration=@/tmp/request.json;type=application/json" \
  --form "template=@/tmp/original.sh" \
  --form "variables=@/tmp/variables.json;type=application/json" \
  --output /tmp/replaced.sh \
  'https://demo.configseeder.com/external/configurations/replace?variablePrefix=%24%7B&variablePostfix=%7D'

Appendix A: ConfigSeeder Management / ValueProvider

A.1. Configuration Options

This chapter lists all configurations available for customization. As the application is built on top of spring-boot, you can configure your application in many different ways (see also: Spring Boot Externalized Configuration).

We recommend following variants:

  1. Passing configuration as environment variables, note that then the variable names needs to be defined in Snake Case.

  2. Pass variables with -- prefix. Example for spring.datasource.url use --spring.datasource.url=jdbc:…​

  3. Pass variables with an externalized configuration --spring.config.location=./ or --spring.config.location=/home/config-server/config.properties (or extension yml)

A.1.1. AesKeyGenerator

Download ConfigSeeder Util from https://configseeder.com/download/#other. This util helps you to generate an AES128 or AES256 key. This key will be used to encrypt ConfigurationValues.

java -jar configseeder-utils-<version>.jar

And select option 1. Follow the instructions shown.

A.1.2. RsaKeyGenerator

Download ConfigSeeder Util from https://configseeder.com/download/#other. This util is helps you generate a private and public key which will be used to encrypt the API Key issued by the config server.

java -jar configseeder-utils-<version>.jar

And select option 2. Follow the instructions shown.

A.2. SSL Certificate

It is recommended to run ConfigSeeder Management and ConfigSeeder ValueProvider with a SSL certificate. This section describes how to create self-signed certificate or signed certificate and how to configure them. Please replace the password changeit with your desired password.

A.2.1. Self-signed certificate

Create a PKCS12 keystore with:

keytool -genkeypair -alias configseeder-local -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650

Define a password (e.g. changeit, at least 6 characters) and your hostname in the CN field:

What is your first and last name?
  [Unknown]: localhost
What is the name of your organizational unit?
  [Unknown]:
What is the name of your organization?
  [Unknown]:
What is the name of your City or Locality?
  [Unknown]:
What is the name of your State or Province?
  [Unknown]:
What is the two-letter country code for this unit?
  [Unknown]:
Is CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
  [no]:  yes

Enter key password for <configseeder-local>
  (RETURN if same as keystore password):

This will generate you a file named keystore.p12 which can be used by ConfigSeeder Management and ConfigSeeder ValueProvider

A.2.2. Signed certificate

These steps assume that you have already a key and a signed certificate:

openssl pkcs12 -export -inkey keystore.key -in keystore.crt -out keystore.p12 -passsword pass:changeit -name configseeder

Note: If no alias was defined, use the number 1 as alias name in the configuration

A.2.3. Configuration

Now configure the application to use the certificate and password.

SSL configurations
server.port=8443 (1)
security.require-ssl=true (2)
server.ssl.key-store-type=PKCS12 (3)
server.ssl.key-store=/path/to/keystore.p12 (4)
server.ssl.key-store-password=notset (5)
server.ssl.key-alias=configseeder (6)
1 Port on which the application gets served
2 Tell security to require requests over HTTPS
3 The format used for the keystore. In our example, PKCS12
4 The path to the keystore containing the certificate (alternative: classpath:keystore.p12)
5 The password used to generate the certificate
6 The alias mapped to the certificate (in our example either configseeder or 1)

Appendix B: Configure Keycloak as Open ID Provider

This chapter explains how to setup Keycloak to work with the config server.

B.1. Setup a new client

  1. Add new realm

    1. Name: configseeder

    2. Create

  2. Realm Settings > General

    1. Display name: ConfigSeeder

  3. Realm Settings > Login

    1. Everything: Off (or according to your needs)

    2. Login with email: On

  4. Realm Settings > Email

    1. Configure a Mailbox E-Mail with a valid E-Mail Address. Example:

      1. Host: mail.domain.com

      2. Port: 587

      3. From Display Name: Keycloak

      4. From: keycloak@domain.com

      5. Enable StartTLS: ON

      6. Enable Authentication: ON

      7. Username: keycloak@domain.com

      8. Password: …​

  5. Clients > Create

    1. Client ID: configseeder

    2. Client Protocol: openid-connect

    3. Save

  6. Clients > configseeder > Settings

    1. Access Type: confidential (with this an additional Tab Credentials) will be shown after save

    2. Valid Redirect URIs: Configure here your URLs. Use Placeholder * at the end. Example: https://configseeder.domain.com/*

  7. Client > configseeder > Credentials

    1. Here you can regenerate the secret if desired

  8. Clients > Mappers > Add Builtin ([x] mandatory, [o] optional)

    1. [x] family name, [x] given name, [x] profile, [x] locale, [x] full name, [x] picture, [o] nickname, [x] email, [x] username

    2. Add selected

B.2. Setup Roles

  1. Roles > Add Role

    1. Add all functional roles (SUPERADMIN, TENANTADMIN, APPLICATIONMANAGER, DEVELOPER, READER)

    2. Add all data roles (depends on your desired configuration group access setup)

    3. Create your own roles you want at the end to assign to the user. Examples:

      1. cs

      2. cs_prod_r

      3. cs_all

      4. SUPERADMIN

      5. DEVELOPER

B.3. Setup Groups

  1. admin

    1. cs, cs_all, SUPERADMIN

  2. dev

    1. cs, cs_prod_r, DEVELOPER

B.4. Setup Users

  1. Add User

  2. Add "username", "firstname", "lastname", "email verified"

  3. Save

  4. Tab "Credentials"

  5. Add "new Password", "Password confirmation", Temporary: Off

  6. Reset Password

  7. Assign Groups

B.5. Fetch needed values for configuring config-server

  • authorization-uri, token-uri, user-info-uri and jwk-set-uri from: Realm Settings > Endpoints > OpenId Endpoint Configuration

  • client-id from: Clients > Client ID

  • client-secret from: Clients > {Client} > Credentials > Secret

Example Keycloak Configuration:

application.properties
spring.security.oauth2.client.provider.keycloak.authorization-uri=   https://<keycloak-server>/auth/realms/<client-id>/protocol/openid-connect/auth
spring.security.oauth2.client.provider.keycloak.token-uri=           https://<keycloak-server>/auth/realms/<client-id>/protocol/openid-connect/token
spring.security.oauth2.client.provider.keycloak.user-info-uri=       https://<keycloak-server>/auth/realms/<client-id>/protocol/openid-connect/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri=         https://<keycloak-server>/auth/realms/<client-id>/protocol/openid-connect/certs
spring.security.oauth2.client.provider.keycloak.user-name-attribute= preferred_username

spring.security.oauth2.client.registration.keycloak.client-id=       <client-id>
spring.security.oauth2.client.registration.keycloak.client-secret=   <client-secret>
spring.security.oauth2.client.registration.keycloak.authorization-grant-type= authorization_code
spring.security.oauth2.client.registration.keycloak.redirect-uri=    {baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.keycloak.scope=           openid,profile
spring.security.oauth2.client.registration.keycloak.clientName=      Keycloak

spring.security.oauth2.client.mapping.keycloak.firstname=            given_name
spring.security.oauth2.client.mapping.keycloak.lastname=             family_name
spring.security.oauth2.client.mapping.keycloak.username=             preferred_username
spring.security.oauth2.client.mapping.keycloak.avatar_url=           picture
spring.security.oauth2.client.mapping.keycloak.accessTokenRolePaths= realm_access.roles

Appendix C: ConfigSeeder Management - Security

C.1. Function Role Privilege Assignment

Table 28. Default Function Role Privilege Mapping (detailed)
Function Role Assigned Privileges

SUPERADMIN

  • MIGRATION

  • LICENSE_UPDATE

  • TENANT_CREATE

  • TENANT_READ

  • TENANT_READ_ALL

  • TENANT_UPDATE

  • TENANT_DELETE

  • TENANT_FULL_STATISTICS

  • TENANT_STATISTICS

  • BINARY_CREATE

  • ENVIRONMENT_CREATE

  • ENVIRONMENT_READ

  • ENVIRONMENT_UPDATE

  • ENVIRONMENT_DELETE

  • API_KEY_CREATE

  • API_KEY_CREATE_CONNECTORS

  • API_KEY_READ

  • API_KEY_UPDATE

  • API_KEY_UPDATE_OWN

  • API_KEY_REVOKE

  • API_KEY_REVOKE_OWN

  • CONFIGURATION_GROUP_CREATE

  • CONFIGURATION_GROUP_READ

  • CONFIGURATION_GROUP_UPDATE

  • CONFIGURATION_GROUP_DELETE

  • VERSIONED_CONFIGURATION_GROUP_CLONE

  • VERSIONED_CONFIGURATION_GROUP_READ

  • VERSIONED_CONFIGURATION_GROUP_UPDATE

  • VERSIONED_CONFIGURATION_GROUP_DELETE

  • VERSIONED_CONFIGURATION_GROUP_RELEASE

  • VERSIONED_CONFIGURATION_GROUP_UNRELEASE

  • CONFIGURATION_ASSEMBLY_CREATE

  • CONFIGURATION_ASSEMBLY_READ

  • CONFIGURATION_ASSEMBLY_UPDATE

  • CONFIGURATION_ASSEMBLY_DELETE

  • CONFIGURATION_NODE_CREATE

  • CONFIGURATION_NODE_READ

  • CONFIGURATION_NODE_UPDATE

  • CONFIGURATION_NODE_UPDATE_KEY

  • CONFIGURATION_NODE_UPDATE_TYPE

  • CONFIGURATION_NODE_DELETE

  • CONFIGURATION_VALUE_CREATE

  • CONFIGURATION_VALUE_READ

  • CONFIGURATION_VALUE_UPDATE

  • CONFIGURATION_VALUE_DELETE

  • CONFIGURATION_VALUE_SYNCHRONIZE

  • ROLE_MAPPING_CREATE

  • ROLE_MAPPING_READ

  • ROLE_MAPPING_DELETE

LICENSEMANAGER

  • LICENSE_UPDATE

  • TENANT_FULL_STATISTICS

  • TENANT_STATISTICS

TENANTADMIN

  • TENANT_READ

  • TENANT_UPDATE

  • TENANT_STATISTICS

  • ENVIRONMENT_CREATE

  • ENVIRONMENT_READ

  • ENVIRONMENT_UPDATE

  • ENVIRONMENT_DELETE

  • API_KEY_CREATE_CONNECTORS

  • API_KEY_READ

  • API_KEY_UPDATE_OWN

  • API_KEY_REVOKE

  • CONFIGURATION_GROUP_CREATE

  • CONFIGURATION_GROUP_READ

  • CONFIGURATION_GROUP_UPDATE

  • CONFIGURATION_GROUP_DELETE

  • VERSIONED_CONFIGURATION_GROUP_READ

  • CONFIGURATION_ASSEMBLY_READ

  • CONFIGURATION_NODE_READ

  • CONFIGURATION_VALUE_READ

APPLICATIONMANAGER

  • TENANT_READ

  • BINARY_CREATE

  • ENVIRONMENT_READ

  • API_KEY_CREATE_CONNECTORS

  • API_KEY_READ

  • API_KEY_UPDATE_OWN

  • API_KEY_REVOKE

  • CONFIGURATION_GROUP_CREATE

  • CONFIGURATION_GROUP_READ

  • CONFIGURATION_GROUP_UPDATE

  • CONFIGURATION_GROUP_DELETE

  • VERSIONED_CONFIGURATION_GROUP_CLONE

  • VERSIONED_CONFIGURATION_GROUP_READ

  • VERSIONED_CONFIGURATION_GROUP_UPDATE

  • VERSIONED_CONFIGURATION_GROUP_DELETE

  • VERSIONED_CONFIGURATION_GROUP_RELEASE

  • VERSIONED_CONFIGURATION_GROUP_UNRELEASE

  • CONFIGURATION_ASSEMBLY_CREATE

  • CONFIGURATION_ASSEMBLY_READ

  • CONFIGURATION_ASSEMBLY_UPDATE

  • CONFIGURATION_ASSEMBLY_DELETE

  • CONFIGURATION_NODE_CREATE

  • CONFIGURATION_NODE_READ

  • CONFIGURATION_NODE_UPDATE

  • CONFIGURATION_NODE_UPDATE_KEY

  • CONFIGURATION_NODE_UPDATE_TYPE

  • CONFIGURATION_NODE_DELETE

  • CONFIGURATION_VALUE_CREATE

  • CONFIGURATION_VALUE_READ

  • CONFIGURATION_VALUE_UPDATE

  • CONFIGURATION_VALUE_DELETE

DEVELOPER

  • TENANT_READ

  • BINARY_CREATE

  • ENVIRONMENT_READ

  • API_KEY_CREATE

  • API_KEY_READ

  • API_KEY_UPDATE_OWN

  • API_KEY_REVOKE_OWN

  • CONFIGURATION_GROUP_READ

  • VERSIONED_CONFIGURATION_GROUP_CLONE

  • VERSIONED_CONFIGURATION_GROUP_READ

  • VERSIONED_CONFIGURATION_GROUP_UPDATE

  • VERSIONED_CONFIGURATION_GROUP_DELETE

  • CONFIGURATION_ASSEMBLY_CREATE

  • CONFIGURATION_ASSEMBLY_READ

  • CONFIGURATION_ASSEMBLY_UPDATE

  • CONFIGURATION_ASSEMBLY_DELETE

  • CONFIGURATION_NODE_CREATE

  • CONFIGURATION_NODE_READ

  • CONFIGURATION_NODE_UPDATE

  • CONFIGURATION_NODE_UPDATE_KEY

  • CONFIGURATION_NODE_DELETE

  • CONFIGURATION_VALUE_CREATE

  • CONFIGURATION_VALUE_READ

  • CONFIGURATION_VALUE_UPDATE

  • CONFIGURATION_VALUE_DELETE

READER

  • TENANT_READ

  • ENVIRONMENT_READ

  • CONFIGURATION_GROUP_READ

  • VERSIONED_CONFIGURATION_GROUP_READ

  • CONFIGURATION_ASSEMBLY_READ

  • CONFIGURATION_NODE_READ

  • CONFIGURATION_VALUE_READ

OPERATOR

  • TENANT_READ

  • ENVIRONMENT_READ

  • CONFIGURATION_GROUP_READ

C.2. Examples of Data Based Roles

Table 29. Tenant Data Role Examples
Tenant Role Infix Tenant Secured Type Access Mode Allowed Privileges

mytenant

DERIVED

READ

cs, cs_all

mytenant

DERIVED

WRITE

cs, cs_all

mytenant

W_ROLE

READ

cs, cs_all

mytenant

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_w

mytenant

RW_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

WRITE

cs_all, cs_mytenant, cs_mytenant_all

mytenant

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_r

mytenant

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_w

Table 30. Configuration Value Data Role Examples
Ten. R.Infix Ten. Sec.Type Env. R.Infix Env. Sec.Type Conf.Group R.Infix Conf.Group Sec.Type Conf.Group Val.Sec.Type Secured AccessMode Allowed Privileges

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

false

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

false

DERIVED

WRITE

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

DERIVED

WRITE

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

W_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_sec_r

mytenant

RW_ROLE

null

DERIVED

team1

DERIVED

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

false

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_sec_r

mytenant

RW_ROLE

null

DERIVED

team1

W_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

null

DERIVED

team1

RW_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

null

DERIVED

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

false

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

false

DERIVED

WRITE

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

DERIVED

WRITE

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

W_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_sec_r

mytenant

RW_ROLE

prod

DERIVED

team1

DERIVED

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

false

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_sec_r

mytenant

RW_ROLE

prod

DERIVED

team1

W_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

prod

DERIVED

team1

RW_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_w

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

prod

DERIVED

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

false

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

W_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_sec_r

mytenant

RW_ROLE

prod

W_ROLE

team1

DERIVED

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

false

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

DERIVED

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant, cs_mytenant_all

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_sec

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_sec_r

mytenant

RW_ROLE

prod

W_ROLE

team1

W_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1, cs_mytenant_team1_all

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

prod

W_ROLE

team1

RW_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_r

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_sec_r

mytenant

RW_ROLE

prod

W_ROLE

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

DERIVED

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod, cs_mytenant_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

W_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod, cs_mytenant_team1_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod, cs_mytenant_team1_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod, cs_mytenant_team1_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod, cs_mytenant_team1_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod, cs_mytenant_team1_prod_all

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

RW_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

RW_ROLE

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

DERIVED

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_prod_all, cs_mytenant_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

W_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

RW_ROLE

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

false

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

false

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

DERIVED

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

DERIVED

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

W_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

W_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

RW_ROLE

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

RW_ROLE

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

READ

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_r

mytenant

RW_ROLE

prod

SEPARATE_RW_ROLES

team1

SEPARATE_RW_ROLES

true

SEPARATE_RW_ROLES

WRITE

cs_all, cs_mytenant_all, cs_mytenant_team1_all, cs_mytenant_team1_prod_all, cs_mytenant_team1_prod_sec_w