Security

Enabling HTTPS on the management API

While on most production configuration the HTTP web API should be behind a loadbalancer in charge of HTTPS encryption, there are case where developers may want to enable HTTPS at the node level.

The api configuration section offers options to add HTTPS bindings to the web API:

"api": {
  //Endpoint used for web server binding
  "bindings": [
    {
      "useHttps": false,
      "endpoints": [ "*:{apiHttpPort}" ]
    },
    {
      "useHttps": true,
      "endpoints": [ "*:{apiHttpsPort}" ],
      "privateKey": {
        "path": "{dataDirectory}/secrets/https.pem",
        "password": "{secrets-cluster-pk-password}"
      }
    }
  ],
  //List of published endpoint (used by clients to connect to the server)
  "published": [
    "http://{loadBalancedIp}:{apiHttpPort}",
    "https://{loadBalancedIp}:{apiHttpsPort}"
  ],

  //Private key used by the web server for HTTPS.
  "privateKey": {
    "path": "{dataDirectory}/secrets/https.pem",
    "password": "{secrets-cluster-pk-password}"
  }

},

Notice that the password is stored in a constant named secrets-cluster-pk-password. In production you may want to make the main configuration file accessible to administrators, but keep secrets like private keys password hidden from them and only accessible by the user running the host process.

To do that, the passwords are stored in another file, declared as an additionnal config file with restricted read permissions:

"configs": {
  // Paths to protected files accessible only by the stormancer process.
  //The content of these files is added to constants at runtime.
  "secrets": "{dataDirectory}/secrets/passwords.json"
},

Note

configuration constants are extensible. By implementing IConfigurationEventHandler node plugins can fetch secrets from alternate sources, for instance Azure, Hashicorp, Google or AWS secure key vaults.

Data protector

The data protector API provides way for grid components to securely store sensitive data like passwords in the configuration file. Passwords for instance are expected to be protected when read from the config file, so putting a password in clear text results in an error while accessing it.

Configuring the data protector

By default, the data protector let .NET core protect data using an application specific key. However, it can be configured to use a private key located in the privateKeyStores:

"security": {
  "privateKeyStores": [
    {
      "path": "{dataDirectory}/secrets"
    }
  ],
  "dataProtection":{
    "privateKey":{
      "path":"key.pem"
    }
  }
},

Note

The data protection API is used to secure secrets in the configuration. It means that no data exposed in config nodes used by the data protection system can be encrypted. The best way to use passwords or secrets in these configuration fields is to put them in a secured sub config file.

Securing config files

Some data in config files are more sensitive than others. To deal with that, the grid supports dividing configuration files in different files with different permissions:

"configs:[
  "subConfig1.json",
  "{dataDirectory}/subConfig2.json",
  ...
]

Note

Only replacement tokens specified in the environment variables and in the constants section of the main config file can be used in the configs array.

When looking for config values, the grid starts first by the main file, then in each sub config file in the order they are defined. This means that any value set in the main file, or in the firsts files cannot be overriden by another file.

In order to secure the configurations, the following steps can be taken:

  • Allow only read access to the main file to the grid user and write access only to root, because users with write access to this file can change the configs array and load any configuration files, defeating protected values.

  • Give write access to non super admin only to subfiles containing values that they should be able to change, and put these files last in the configs array to prevent overriding of any config parameter set in a file with higher priority.

  • Store secrets in one or several read/write protected files, with the metadata secrets set to true for these files.

Note

Plugins could load parts of config trees from secured config vaults.

Secrets file metadata

Config files can contain an optional meta node used by the grid to decide how to treat data contained in the file:

"meta":{
  "secrets":true
}

By setting this metadata to true, you inform the grid that the file contains sensitive information. Any constants defined in a config file with this metadata set are automatically encrypted when referenced by a replacement token in another file without :code:`secrets`set to true.

This protects from malicious users that would try to use a password constant in a field displayed in the logs or in a web API (like published Endpoints). Only specific APIs in the grid ask for encrypted data to be decoded.

Note

encryption is not applied when using constants in other secrets files.

Note

It’s not authorized to reference a non secret constant in a secret constant.

Encrypting config values

Secrets files are useful, but sometime it’s simplier to put secrets in not protected config files. The configuration system supports decrypting configuration values (string only for now) encrypted using the data protection API:

{
  "secretValue":"encrypted:xxxxxxxxxx"
}

To encrypt a string that will be decrypted by grid systems expecting secrets, use the stormancer grid executable:

dotnet stormancer.dll encrypt-data --content "myPassword" -c config

Note

If the data protection key is changed, all encrypted secrets using this method will become invalid and need to be generated again.

Note

If the data protection private key is shared between different nodes, the encrypted value can be shared between nodes too.

Certificate stores

Supplying a private key password

Some certificates may be encrypted by a password. Most certificate stores support providing one in the certificate claims by setting the password configuration field:

"privateKey":{
    "password":"yyyy"
}

Note

To prevent password retrieval from the command line, the password is expected to be encrypted using the node data protector API.

File provider

File certificate stores are directories in the file system with read access enabled to the Stormancer node. They are declared in the configuration by providing the path to a directory that is going to be searched for when looking for certificates.

{
    "path": "/etc/security/stormancer/clusters"
}

When looking for certificates in a file store, the easiest way is to provide a relative path:

"privateKey: {
    "path:"pk.pem"
}

Note

The provider doesn’t give access to certificates outside of its path directory.

The file provider looks for certificates by filename only. In federation setups, public keys are looked up by subject by the platform, so a way to map subjects to file paths is provided:

{
    "path": "/etc/security/stormancer/clusters",
    "pattern":"{subject}/*.cer"
}

The provided pattern returns all certificates in a subdirectory of the store path with the same name as the searched subject.

If a pattern is provided in the store, path provided in the certificate is ignored, but is available as a pattern format token. So the following pattern is exactly equivalent to not providing a pattern:

"pattern": "{path}"

Certificate store provider

The certificate store provider makes use of the store API provided by .NET Core to search certificates by subject or by thumbprint in cert:/CurrentUser/My or cert:/LocalMachine/My:

{
    "store":"cert:/CurrentUser/My"
}

In these stores, certificates can be searched either by thumbprint or by subject:

"privateKey": {
    "thumbprint:"xxxxxxxxx"
}

or:

"privateKey":{
    "subject":"server.stormancer.com"
}

If both parameters are supplied, a search by thumbprint is done.

AWS Certificate provider

The provider search certificates in base64 form in the AWS Secret manager.

Certificates are indexed by AWS resource id and are located using the secretId string, which must start by arn::

"privateKey":{
    "secretId":"arn:xxxxxxx"
}

The store configuration must specify the aws access key id, the secret access key and the region:

{
    "aws-accessKeyId":"xxx",
    "aws-secretAccessKey":"yyy",
    "aws-region":"zzz"

}

Note

The aws-secretAccessKey must be protected.