6.1 KiB
Configuration Validation
We use a fork of https://github.com/go-playground/validator which can be found at https://github.com/letsencrypt/validator.
Usage
By default Boulder validates config files for all components with a registered validator. Validating a config file for a given component is as simple as running the component directly:
$ ./bin/boulder-observer -config test/config-next/observer.yml
Error validating config file "test/config-next/observer.yml": Key: 'ObsConf.MonConfs[1].Kind' Error:Field validation for 'Kind' failed on the 'oneof' tag
or by running the boulder
binary and passing the component name as a
subcommand:
$ ./bin/boulder boulder-observer -config test/config-next/observer.yml
Error validating config file "test/config-next/observer.yml": Key: 'ObsConf.MonConfs[1].Kind' Error:Field validation for 'Kind' failed on the 'oneof' tag
Struct Tag Tips
You can find the full list of struct tags supported by the validator [here] (https://pkg.go.dev/github.com/go-playground/validator/v10#section-documentation). The following are some tips for struct tags that are commonly used in our configuration files.
required
The required tag means that the field is not allowed to take its zero value, or equivalently, is not allowed to be omitted. Note that this does not validate that slices or maps have contents, it simply guarantees that they are not nil. For fields of those types, you should use min=1 or similar to ensure they are not empty.
There are also "conditional" required tags, such as required_with
,
required_with_all
, required_without
, required_without_all
, and
required_unless
. These behave exactly like the basic required tag, but only if
their conditional (usually the presence or absence of one or more other named
fields) is met.
omitempty
The omitempty tag allows a field to be empty, or equivalently, to take its zero value. If the field is omitted, none of the other validation tags on the field will be enforced. This can be useful for tags like validate="omitempty,url", for a field which is optional, but must be a URL if it is present.
The omitempty tag can be "overruled" by the various conditional required tags.
For example, a field with tag validate="omitempty,url,required_with=Foo"
is
allowed to be empty when field Foo is not present, but if field Foo is present,
then this field must be present and must be a URL.
-
Normally, config validation descends into all struct-type fields, recursively validating their fields all the way down. Sometimes this can pose a problem, when a nested struct declares one of its fields as required, but a parent struct wants to treat the whole nested struct as optional. The "-" tag tells the validation not to recurse, marking the tagged field as optional, and therefore making all of its sub-fields optional as well. We use this tag for many config duration and password file struct valued fields which are optional in some configs but required in others.
structonly
The structonly tag allows a struct valued field to be empty, or equivalently, to
take its zero value, if it's not "overruled" by various conditional tags. If the
field is omitted the recursive validation of the structs fields will be skipped.
This can be useful for tags like validate:"required_without=Foo,structonly"
for a struct valued field which is only required, and thus should only be
validated, if field Foo
is not present.
min=1
, gte=1
These validate that the value of integer valued field is greater than zero and that the length of the slice or map is greater than zero.
For instance, the following would be valid config for a slice valued field
tagged with required
.
{
"foo": [],
}
But, only the following would be valid config for a slice valued field tagged
with min=1
.
{
"foo": ["bar"],
}
len
Same as eq
(equal to) but can also be used to validate the length of the
strings.
hostname_port
The
docs
for this tag are scant with detail, but it validates that the value is a valid
RFC 1123 hostname and port. It is used to validate many of the
ListenAddress
and DebugAddr
fields of our components.
Future Work
This tag is compatible with IPv4 addresses, but not IPv6 addresses. We should consider fixing this in our fork of the validator.
dive
This tag is used to validate the values of a slice or map. For instance, the
following would be valid config for a slice valued field ([]string
) tagged
with min=1,dive,oneof=bar baz
.
{
"foo": ["bar", "baz"],
}
Note that the dive
tag introduces an order-dependence in writing tags: tags
that come before dive
apply to the current field, while tags that come after
dive
apply to the current field's child values. In the example above: min=1
applies to the length of the slice ([]string
), while oneof=bar baz
applies
to the value of each string in the slice.
We can also use dive
to validate the values of a map. For instance, the
following would be valid config for a map valued field (map[string]string
)
tagged with min=1,dive,oneof=one two
.
{
"foo": {
"bar": "one",
"baz": "two"
},
}
dive
can also be invoked multiple times to validate the values of nested
slices or maps. For instance, the following would be valid config for a slice of
slice valued field ([][]string
) tagged with min=1,dive,min=2,dive,oneof=bar baz
.
{
"foo": [
["bar", "baz"],
["baz", "bar"],
],
}
min=1
will be applied to the outer slice ([]
).min=2
will be applied to inner slice ([]string
).oneof=bar baz
will be applied to each string in the inner slice.
keys
and endkeys
These tags are used to validate the keys of a map. For instance, the following
would be valid config for a map valued field (map[string]string
) tagged with
min=1,dive,keys,eq=1|eq=2,endkeys,required
.
{
"foo": {
"1": "bar",
"2": "baz",
},
}
min=1
will be applied to the map itselfeq=1|eq=2
will be applied to the map keysrequired
will be applied to map values