Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ACME_SERVER_URL=https://pebble:14000/dir
DNS_ADDRESS=challtestsrv:8053
CERTIFICATOR_DOMAINS=mydomain.com,example.com
CERTIFICATOR_RENEW_BEFORE_DAYS=30
CERTIFICATOR_DOMAINS_LIST=mydomain.com,example.com
CERTIFICATOR_DOMAINS_FILE=/app/fixtures/domains.yml
ENVIRONMENT=dev
EXEC_PATH=./fixtures/update-dns.sh
Expand Down
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Configuration variables:
- `LOG_LEVEL` - logging level, supported levels - DEBUG, INFO, WARN, ERROR, FATAL. Default: INFO.
- `DNS_ADDRESS` - DNS server address that is used to check challenge DNS record propagation. Default: 127.0.0.1:53
- `ENVIRONMENT` - sets an environment where the certificator is running. If the environment is dev it uses token set in `VAULT_DEV_ROOT_TOKEN_ID` env variable to authenticate in Vault. If the environment is prod it uses an approle authentication method. Default: prod
- `CERTIFICATOR_DOMAINS_FILE` - path to a file where domains are defined. Default: /code/domains.yml
- `CERTIFICATOR_DOMAINS_FILE` - path to a file where domains are defined. Overridden by `CERTIFICATOR_DOMAINS_LIST`. Default: /code/domains.yml
- `CERTIFICATOR_DOMAINS_LIST` - allows specifying domains directly via an environment variable. If set(non-empty), this takes precedence over loading domains from the DomainsFile (`CERTIFICATOR_DOMAINS_FILE`).
- `CERTIFICATOR_RENEW_BEFORE_DAYS` - set how many validity days should certificate have remaining before renewal. Default: 30

#### CNAME
Expand All @@ -45,9 +46,31 @@ CA will verify domain ownership following the same scheme

This allows giving this tool a token with access rights limited to a single DNS zone.

#### Domains file
#### Domains

Domains that the certificator should retrieve certificates for should be defined in this file in YAML format. An example file is in [domains.yml](domains.yml).
The application supports two ways to configure the list of domains that it should retrieve certificates for:

### 1. Environment Variable: `CERTIFICATOR_DOMAINS_LIST`

You can specify the list of domains directly via the `CERTIFICATOR_DOMAINS_LIST` environment variable. This is useful for containerised deployments or environments where editing files is inconvenient. The value should be a comma-separated list of domains.

**Example:**

```sh
export CERTIFICATOR_DOMAINS_LIST=example.com,example.org,sub.example.net
```

Note: **If this variable is set (non-empty), it takes precedence over file-based configuration.**

### 2. Domains File: `CERTIFICATOR_DOMAINS_FILE`

If `CERTIFICATOR_DOMAINS_LIST` is not set, the application will load domains from a YAML file specified by the `CERTIFICATOR_DOMAINS_FILE` environment variable. An example file is in [domains.yml](domains.yml), which is deployed to `/code/domains.yml` in the container and is the default value for this variable.

**Example:**

```sh
export CERTIFICATOR_DOMAINS_FILE=/path/to/my_domains.yml
```

Every item in the array under the `domains` key results in a certificate. The first domain in an array item is used for the CommonName field of the certificate, all other domains are added using the Subject Alternate Names extension. Domains in a single array item are separated by commas. The first domain is also used as a key in the Vault KV store.

Expand Down
38 changes: 31 additions & 7 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ type Config struct {
DNSAddress string `envconfig:"DNS_ADDRESS" default:"127.0.0.1:53"`
Environment string `envconfig:"ENVIRONMENT" default:"prod"`
DomainsFile string `envconfig:"CERTIFICATOR_DOMAINS_FILE" default:"/code/domains.yml"`
DomainsList []string `envconfig:"CERTIFICATOR_DOMAINS_LIST"`
RenewBeforeDays int `envconfig:"CERTIFICATOR_RENEW_BEFORE_DAYS" default:"30"`
Domains []string `yaml:"domains"`
Domains []string
}

// LoadConfig loads configuration options to variable
Expand All @@ -50,19 +51,42 @@ func LoadConfig() (Config, error) {
return Config{}, errors.Wrapf(err, "failed getting config from env")
}

f, err := os.Open(cfg.DomainsFile)
if len(cfg.DomainsList) > 0 {
cfg.Domains = cfg.DomainsList

return cfg, err
} else {
cfg.Domains, err = parseDomainsFile(cfg.DomainsFile)
if err != nil {
return Config{}, err
}

return cfg, err
}
}

func parseDomainsFile(domainsFile string) ([]string, error) {
f, err := os.Open(domainsFile)
if err != nil {
return Config{}, errors.Wrapf(err, "opening %s", cfg.DomainsFile)
return nil, errors.Wrapf(err, "opening %s", domainsFile)
}

content, err := ioutil.ReadAll(f)
if err != nil {
return Config{}, errors.Wrapf(err, "reading content of %s", cfg.DomainsFile)
return nil, errors.Wrapf(err, "reading content of %s", domainsFile)
}

if err := yaml.Unmarshal(content, &cfg); err != nil {
return Config{}, errors.Wrapf(err, "parsing %s", cfg.DomainsFile)
var contentMap map[string]interface{}

if err := yaml.Unmarshal(content, &contentMap); err != nil {
return nil, errors.Wrapf(err, "parsing %s", domainsFile)
}

var domains []string

for _, v := range contentMap["domains"].([]interface{}) {
domains = append(domains, v.(string))
}

return cfg, err
return domains, nil
}
66 changes: 65 additions & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestDefaultConfig(t *testing.T) {
Environment: "prod",
DomainsFile: "../../domains.yml",
Domains: []string{"mydomain.com,www.mydomain.com", "example.com"},
DomainsList: nil,
RenewBeforeDays: 30,
}

Expand All @@ -40,7 +41,7 @@ func TestDefaultConfig(t *testing.T) {
testutil.Equals(t, expectedConf, conf)
}

func TestConfig(t *testing.T) {
func TestConfig_WithDomainsFile(t *testing.T) {
var (
reregisterAcc bool = true
acmeServerURL string = "http://someserver"
Expand Down Expand Up @@ -76,6 +77,7 @@ func TestConfig(t *testing.T) {
Environment: environment,
DomainsFile: "../../domains.yml",
Domains: []string{"mydomain.com,www.mydomain.com", "example.com"},
DomainsList: nil,
RenewBeforeDays: renewBeforeDays,
}
)
Expand All @@ -100,6 +102,68 @@ func TestConfig(t *testing.T) {
testutil.Equals(t, expectedConf, conf)
}

func TestConfig_WithDomainsList(t *testing.T) {
var (
reregisterAcc bool = true
acmeServerURL string = "http://someserver"
dnsChallengeProvider string = "other"
dnsPropagationReq bool = false
vaultRoleID string = "role"
vaultSecretID string = "secret"
vaultKVStorePath string = "secret/path"
logFormat string = "LOGFMT"
logLevel string = "DEBUG"
dnsAddress string = "1.1.1.1:53"
environment string = "test"
renewBeforeDays int = 60

expectedConf = Config{
Acme: Acme{
AccountEmail: "test@test.com",
DNSChallengeProvider: dnsChallengeProvider,
DNSPropagationRequirement: dnsPropagationReq,
ReregisterAccount: reregisterAcc,
ServerURL: acmeServerURL,
},
Vault: Vault{
ApproleRoleID: vaultRoleID,
ApproleSecretID: vaultSecretID,
KVStoragePath: vaultKVStorePath,
},
Log: Log{
Format: logFormat,
Level: logLevel,
},
DNSAddress: dnsAddress,
Environment: environment,
DomainsFile: "../../domains.yml",
DomainsList: []string{"mydomain.com", "www.mydomain.com", "example.com"},
Domains: []string{"mydomain.com", "www.mydomain.com", "example.com"},
RenewBeforeDays: renewBeforeDays,
}
)

resetEnvVars()

os.Setenv("ACME_REREGISTER_ACCOUNT", strconv.FormatBool(reregisterAcc))
os.Setenv("ACME_SERVER_URL", acmeServerURL)
os.Setenv("ACME_DNS_CHALLENGE_PROVIDER", dnsChallengeProvider)
os.Setenv("ACME_DNS_PROPAGATION_REQUIREMENT", strconv.FormatBool(dnsPropagationReq))
os.Setenv("VAULT_APPROLE_ROLE_ID", vaultRoleID)
os.Setenv("VAULT_APPROLE_SECRET_ID", vaultSecretID)
os.Setenv("VAULT_KV_STORAGE_PATH", vaultKVStorePath)
os.Setenv("LOG_FORMAT", logFormat)
os.Setenv("LOG_LEVEL", logLevel)
os.Setenv("DNS_ADDRESS", dnsAddress)
os.Setenv("ENVIRONMENT", environment)
os.Setenv("CERTIFICATOR_RENEW_BEFORE_DAYS", strconv.Itoa(renewBeforeDays))
os.Setenv("CERTIFICATOR_DOMAINS_LIST", "mydomain.com,www.mydomain.com,example.com")

conf, err := LoadConfig()
testutil.Ok(t, err)
testutil.Equals(t, expectedConf, conf)
}

func resetEnvVars() {
// Set required env vars
os.Setenv("ACME_ACCOUNT_EMAIL", "test@test.com")
Expand Down