feat(secretstores): Add systemd-credentials plugin (#13657)
This commit is contained in:
parent
f2cc928178
commit
2dc8e436db
2
go.mod
2
go.mod
|
|
@ -63,6 +63,7 @@ require (
|
||||||
github.com/coocood/freecache v1.2.3
|
github.com/coocood/freecache v1.2.3
|
||||||
github.com/coreos/go-semver v0.3.1
|
github.com/coreos/go-semver v0.3.1
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0
|
||||||
github.com/couchbase/go-couchbase v0.1.1
|
github.com/couchbase/go-couchbase v0.1.1
|
||||||
github.com/digitalocean/go-libvirt v0.0.0-20220811165305-15feff002086
|
github.com/digitalocean/go-libvirt v0.0.0-20220811165305-15feff002086
|
||||||
github.com/dimchansky/utfbom v1.1.1
|
github.com/dimchansky/utfbom v1.1.1
|
||||||
|
|
@ -321,6 +322,7 @@ require (
|
||||||
github.com/goburrow/serial v0.1.1-0.20211022031912-bfb69110f8dd // indirect
|
github.com/goburrow/serial v0.1.1-0.20211022031912-bfb69110f8dd // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -1029,6 +1029,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
|
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
|
||||||
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
|
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
|
||||||
|
|
@ -1234,6 +1236,8 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+
|
||||||
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ This folder contains the plugins for the secret-store functionality:
|
||||||
* http: Query secrets from an HTTP endpoint
|
* http: Query secrets from an HTTP endpoint
|
||||||
* jose: Javascript Object Signing and Encryption
|
* jose: Javascript Object Signing and Encryption
|
||||||
* os: Native tooling provided on Linux, MacOS, or Windows.
|
* os: Native tooling provided on Linux, MacOS, or Windows.
|
||||||
|
* systemd: Secret-store to access systemd secrets
|
||||||
|
|
||||||
See each plugin's README for additional details.
|
See each plugin's README for additional details.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build !custom || secretstores || secretstores.systemd
|
||||||
|
|
||||||
|
package all
|
||||||
|
|
||||||
|
import _ "github.com/influxdata/telegraf/plugins/secretstores/systemd" // register plugin
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
|
||||||
|
# Systemd Secret-Store Plugin
|
||||||
|
|
||||||
|
The `systemd` plugin allows utilizing credentials and secrets provided by
|
||||||
|
[systemd][] to the Telegraf service. Systemd ensures that only the intended
|
||||||
|
service can access the credentials for the lifetime of this service. The
|
||||||
|
credentials appear as plaintext files to the consuming service but are stored
|
||||||
|
encrypted on the host system. This encryption can also use TPM2 protection if
|
||||||
|
available (see [this article][systemd-descr] for details).
|
||||||
|
|
||||||
|
This plugin does not support setting the credentials. See the
|
||||||
|
[credentials management section](#credential-management) below for how to
|
||||||
|
setup systemd credentials and how to add credentials
|
||||||
|
|
||||||
|
**Note**: Secrets of this plugin are static and are not updated after startup.
|
||||||
|
|
||||||
|
## Requirements and caveats
|
||||||
|
|
||||||
|
This plugin requires **systemd version 250+** with correctly set-up credentials
|
||||||
|
via [systemd-creds][] (see [setup section](#credential-management)).
|
||||||
|
However, to use `ImportCredential`, as done in the default service file, you
|
||||||
|
need **systemd version 254+** otherwise you need to specify the credentials
|
||||||
|
using `LoadCredentialEncrypted` in a service-override.
|
||||||
|
|
||||||
|
In the default setup, Telegraf expects credential files to be prefixed with
|
||||||
|
`telegraf.` and without a custom name setting (i.e. no `--name`).
|
||||||
|
|
||||||
|
It is important to note that when TPM2 sealing is available on the host,
|
||||||
|
credentials can only be created and used on the **same machine** as decrypting
|
||||||
|
the secrets requires the encryption key *and* a key stored in TPM2. Therefore,
|
||||||
|
creating credentials and then copying it to another machine will fail!
|
||||||
|
|
||||||
|
Please be aware that, due to its nature, this plugin is **ONLY** available
|
||||||
|
when started as a service. It does **NOT** find any credentials when started
|
||||||
|
manually via the command line! Therefore, `secrets` commands should **not**
|
||||||
|
be used with this plugin.
|
||||||
|
|
||||||
|
## Usage <!-- @/docs/includes/secret_usage.md -->
|
||||||
|
|
||||||
|
Secrets defined by a store are referenced with `@{<store-id>:<secret_key>}`
|
||||||
|
the Telegraf configuration. Only certain Telegraf plugins and options of
|
||||||
|
support secret stores. To see which plugins and options support
|
||||||
|
secrets, see their respective documentation (e.g.
|
||||||
|
`plugins/outputs/influxdb/README.md`). If the plugin's README has the
|
||||||
|
`Secret-store support` section, it will detail which options support secret
|
||||||
|
store usage.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```toml @sample.conf
|
||||||
|
# Secret-store to access systemd secrets
|
||||||
|
[[secretstores.systemd]]
|
||||||
|
## Unique identifier for the secretstore.
|
||||||
|
## This id can later be used in plugins to reference the secrets
|
||||||
|
## in this secret-store via @{<id>:<secret_key>} (mandatory)
|
||||||
|
id = "systemd"
|
||||||
|
|
||||||
|
## Path to systemd credentials directory
|
||||||
|
## This should not be required as systemd indicates this directory
|
||||||
|
## via the CREDENTIALS_DIRECTORY environment variable.
|
||||||
|
# path = "${CREDENTIALS_DIRECTORY}"
|
||||||
|
|
||||||
|
## Prefix to remove from systemd credential-filenames to derive secret names
|
||||||
|
# prefix = "telegraf."
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Each Secret provided by systemd will be available as file under
|
||||||
|
`${CREDENTIALS_DIRECTORY}/<secret-name>` for the service. You will **not** be
|
||||||
|
able to see them as a regular, non-telegraf user. Credential visibility from
|
||||||
|
other systemd services is mediated by the `User=` and `PrivateMounts=`
|
||||||
|
service-unit directives for those services. See the
|
||||||
|
[systemd.exec man-page][systemd-exec] for details.
|
||||||
|
|
||||||
|
## Credential management
|
||||||
|
|
||||||
|
Most steps here are condensed from the [systemd-creds man-page][systemd-creds]
|
||||||
|
and are using this command. Please also check that man-page as the options
|
||||||
|
or verbs used here might be outdated for the systemd version you are using.
|
||||||
|
|
||||||
|
**Please note**: We are using `/etc/credstore.encrypted` as our storage
|
||||||
|
location for encrypted credentials throughout the examples below and assuming
|
||||||
|
a Telegraf install via package manager. If you are using some other means to
|
||||||
|
install Telegraf you might need to create that directory.
|
||||||
|
Furthermore, we assume the secret-store ID to be set to `systemd` in the
|
||||||
|
examples.
|
||||||
|
|
||||||
|
Setting up systemd-credentials might vary on your distribution or version so
|
||||||
|
please also check the documentation there. You might also need to install
|
||||||
|
supporting packages such as `tpm2-tools`.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
If you have not done it already, systemd requires a first-time setup of the
|
||||||
|
credential system. If you are planning to use the TPM2 chip of your system
|
||||||
|
for protecting the credentials, you should first make sure that it is
|
||||||
|
available using
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo systemd-creds has-tpm2
|
||||||
|
```
|
||||||
|
|
||||||
|
The output should look similar to
|
||||||
|
|
||||||
|
```text
|
||||||
|
partial
|
||||||
|
-firmware
|
||||||
|
+driver
|
||||||
|
+system
|
||||||
|
+subsystem
|
||||||
|
```
|
||||||
|
|
||||||
|
If TPM2 is available on your system, credentials can also be tied to the device
|
||||||
|
by utilizing TPM2 sealing. See the [systemd-creds man-page][systemd-creds] for
|
||||||
|
details.
|
||||||
|
|
||||||
|
Now setup the credentials by creating the root key.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo systemd-creds setup
|
||||||
|
```
|
||||||
|
|
||||||
|
A warning may appears if you are storing the generated key on an unencrypted
|
||||||
|
disk which is not recommended. With this, we are all set to create credentials.
|
||||||
|
|
||||||
|
### Creating credentials
|
||||||
|
|
||||||
|
After setting up the encryption key we can create a new credential using
|
||||||
|
|
||||||
|
```shell
|
||||||
|
echo -n "john-doe-jr" | sudo systemd-creds encrypt - /etc/credstore.encrypted/telegraf.http_user
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now have a file named `telegraf.http_user` containing the encrypted
|
||||||
|
username. The secret-store later provides the secret using this filename as the
|
||||||
|
secret's key.
|
||||||
|
**Please note:**: By default Telegraf strips the `telegraf.` prefix. If you use
|
||||||
|
a different prefix or no prefix at all you need to adapt the `prefix` setting!
|
||||||
|
|
||||||
|
We can now add more secrets. To avoid potentially leaking the plain-text
|
||||||
|
credentials through command-history or showing it on the screen we use
|
||||||
|
|
||||||
|
```shell
|
||||||
|
systemd-ask-password -n | sudo systemd-creds encrypt - /etc/credstore.encrypted/telegraf.http_password
|
||||||
|
```
|
||||||
|
|
||||||
|
to interactively enter the password.
|
||||||
|
|
||||||
|
### Using credentials as secrets
|
||||||
|
|
||||||
|
To use the credentials as secrets you need to first instantiate a `systemd`
|
||||||
|
secret-store by adding
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[secretstores.systemd]]
|
||||||
|
id = "systemd"
|
||||||
|
```
|
||||||
|
|
||||||
|
to your Telegraf configuration. Assuming the two example credentials
|
||||||
|
`http_user` and `http_password` you can now use those as secrets via
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[inputs.http]]
|
||||||
|
urls = ["http://localhost/metrics"]
|
||||||
|
username = "@{systemd:http_user}"
|
||||||
|
password = "@{systemd:http_password}"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
in your plugins.
|
||||||
|
|
||||||
|
### Chaining for unattended start
|
||||||
|
|
||||||
|
When using many secrets or when secrets need to be shared among hosts, listing
|
||||||
|
all of them in the service file might be cumbersome. Additionally, it is hard
|
||||||
|
to manually test Telegraf configurations with the `systemd` secret-store as
|
||||||
|
those secrets are only available when started as a service.
|
||||||
|
|
||||||
|
Here, secret-store chaining comes into play, denoting a setup where one
|
||||||
|
secret-store, in our case `secretstores.systemd`, is used to unlock another
|
||||||
|
secret-store (`secretstores.jose` in this example).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[secretstores.systemd]]
|
||||||
|
id = "systemd"
|
||||||
|
|
||||||
|
[[secretstores.jose]]
|
||||||
|
id = "mysecrets"
|
||||||
|
path = "/etc/telegraf/secrets"
|
||||||
|
password = "@{systemd:initial}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we assume that an `initial` credential was injected through the service
|
||||||
|
file. This `initial` secret is then used to unlock the `jose` secret-store
|
||||||
|
which might provide many different secrets backed by encrypted files.
|
||||||
|
|
||||||
|
Input and output plugins can the use the `jose` secrets (via `@{mysecrets:...}`)
|
||||||
|
to fill sensitive data such as usernames, passwords or tokens.
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
Please always make sure your systemd version matches Telegraf's requirements,
|
||||||
|
i.e. you do have version 254 or later.
|
||||||
|
|
||||||
|
When not being able to start the service please check the logs. A common issue
|
||||||
|
is using the `--name` option which does not work with systemd's
|
||||||
|
`ImportCredential` setting.
|
||||||
|
a mismatch between the name stored in the credential (given during
|
||||||
|
`systemd-creds encrypt`) and the one used in the
|
||||||
|
`LoadCredentialEncrypted` statement.
|
||||||
|
|
||||||
|
In case you are having trouble referencing credentials in Telegraf, you should
|
||||||
|
check what is available via
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CREDENTIALS_DIRECTORY=/etc/credstore.encrypted sudo systemd-creds list
|
||||||
|
```
|
||||||
|
|
||||||
|
for the example above you should see
|
||||||
|
|
||||||
|
```text
|
||||||
|
NAME SECURE SIZE PATH
|
||||||
|
-------------------------------------------------------------------
|
||||||
|
telegraf.http_password insecure 146B /etc/credstore.encrypted/telegraf.http_password
|
||||||
|
telegraf.http_user insecure 142B /etc/credstore.encrypted/telegraf.http_user
|
||||||
|
```
|
||||||
|
|
||||||
|
**Please note**: Telegraf's secret management functionality is not helpful here
|
||||||
|
as credentials are *only* available to the systemd service, not via commandline.
|
||||||
|
|
||||||
|
Remember to remove the `prefix` configured in your secret-store from the `NAME`
|
||||||
|
column to get the secrets' `key`.
|
||||||
|
|
||||||
|
To get the actual value of a credential use
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo systemd-creds decrypt /etc/credstore.encrypted/telegraf.http_password -
|
||||||
|
```
|
||||||
|
|
||||||
|
Please use the above command(s) with care as they do reveal the secret value
|
||||||
|
of the credential!
|
||||||
|
|
||||||
|
[systemd]: https://www.freedesktop.org/wiki/Software/systemd/
|
||||||
|
[systemd-descr]: https://systemd.io/CREDENTIALS
|
||||||
|
[systemd-creds]: https://www.freedesktop.org/software/systemd/man/systemd-creds.html
|
||||||
|
[systemd-exec]: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Secret-store to access systemd secrets
|
||||||
|
[[secretstores.systemd]]
|
||||||
|
## Unique identifier for the secretstore.
|
||||||
|
## This id can later be used in plugins to reference the secrets
|
||||||
|
## in this secret-store via @{<id>:<secret_key>} (mandatory)
|
||||||
|
id = "systemd"
|
||||||
|
|
||||||
|
## Path to systemd credentials directory
|
||||||
|
## This should not be required as systemd indicates this directory
|
||||||
|
## via the CREDENTIALS_DIRECTORY environment variable.
|
||||||
|
# path = "${CREDENTIALS_DIRECTORY}"
|
||||||
|
|
||||||
|
## Prefix to remove from systemd credential-filenames to derive secret names
|
||||||
|
# prefix = "telegraf."
|
||||||
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
//go:generate ../../../tools/readme_config_includer/generator
|
||||||
|
package systemd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/go-systemd/v22/dbus"
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/secretstores"
|
||||||
|
)
|
||||||
|
|
||||||
|
const systemdMinimumVersion = 250
|
||||||
|
|
||||||
|
// Required to be a variable to mock the version in tests
|
||||||
|
var getSystemdVersion = getSystemdMajorVersion
|
||||||
|
|
||||||
|
//go:embed sample.conf
|
||||||
|
var sampleConfig string
|
||||||
|
|
||||||
|
type Systemd struct {
|
||||||
|
Path string `toml:"path"`
|
||||||
|
Prefix string `toml:"prefix"`
|
||||||
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Systemd) SampleConfig() string {
|
||||||
|
return sampleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init initializes all internals of the secret-store
|
||||||
|
func (s *Systemd) Init() error {
|
||||||
|
version, err := getSystemdVersion()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to detect systemd version: %w", err)
|
||||||
|
}
|
||||||
|
s.Log.Debugf("Found systemd version %d...", version)
|
||||||
|
if version < systemdMinimumVersion {
|
||||||
|
return fmt.Errorf("systemd version %d below minimum version %d", version, systemdMinimumVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default the credentials directory is passed in by systemd
|
||||||
|
// via the "CREDENTIALS_DIRECTORY" environment variable.
|
||||||
|
defaultPath := os.Getenv("CREDENTIALS_DIRECTORY")
|
||||||
|
if defaultPath == "" {
|
||||||
|
s.Log.Warn("CREDENTIALS_DIRECTORY environment variable undefined. Make sure credentials are setup correctly!")
|
||||||
|
if s.Path == "" {
|
||||||
|
return errors.New("'path' required without CREDENTIALS_DIRECTORY")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use default path if no explicit was specified. This should be the common case.
|
||||||
|
if s.Path == "" {
|
||||||
|
s.Path = defaultPath
|
||||||
|
}
|
||||||
|
s.Path, err = filepath.Abs(s.Path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot determine absolute path of %q: %w", s.Path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can access the target directory
|
||||||
|
if _, err := os.Stat(s.Path); err != nil {
|
||||||
|
return fmt.Errorf("accessing credentials directory %q failed: %w", s.Path, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Systemd) Get(key string) ([]byte, error) {
|
||||||
|
secretFile, err := filepath.Abs(filepath.Join(s.Path, s.Prefix+key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if filepath.Dir(secretFile) != s.Path {
|
||||||
|
return nil, fmt.Errorf("invalid directory detected for key %q", key)
|
||||||
|
}
|
||||||
|
value, err := os.ReadFile(secretFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot read the secret's value: %w", err)
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Systemd) List() ([]string, error) {
|
||||||
|
secretFiles, err := os.ReadDir(s.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot read files: %w", err)
|
||||||
|
}
|
||||||
|
secrets := make([]string, 0, len(secretFiles))
|
||||||
|
for _, entry := range secretFiles {
|
||||||
|
key := strings.TrimPrefix(entry.Name(), s.Prefix)
|
||||||
|
secrets = append(secrets, key)
|
||||||
|
}
|
||||||
|
return secrets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Systemd) Set(_, _ string) error {
|
||||||
|
return errors.New("secret-store does not support creating secrets")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResolver returns a function to resolve the given key.
|
||||||
|
func (s *Systemd) GetResolver(key string) (telegraf.ResolveFunc, error) {
|
||||||
|
resolver := func() ([]byte, bool, error) {
|
||||||
|
s, err := s.Get(key)
|
||||||
|
return s, false, err
|
||||||
|
}
|
||||||
|
return resolver, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSystemdMajorVersion() (int, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
conn, err := dbus.NewWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
fullVersion, err := conn.GetManagerProperty("Version")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
fullVersion = strings.Trim(fullVersion, "\"")
|
||||||
|
return strconv.Atoi(strings.SplitN(fullVersion, ".", 2)[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the secret-store on load.
|
||||||
|
func init() {
|
||||||
|
secretstores.Add("systemd", func(_ string) telegraf.SecretStore {
|
||||||
|
return &Systemd{Prefix: "telegraf."}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package systemd
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package systemd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getSystemdVersionMin() (int, error) {
|
||||||
|
return systemdMinimumVersion, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSampleConfig(t *testing.T) {
|
||||||
|
plugin := &Systemd{}
|
||||||
|
require.NotEmpty(t, plugin.SampleConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinimumVersion(t *testing.T) {
|
||||||
|
getSystemdVersion = func() (int, error) { return 123, nil }
|
||||||
|
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.ErrorContains(t, plugin.Init(), "below minimum version")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyPath(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.ErrorContains(t, plugin.Init(), "'path' required without CREDENTIALS_DIRECTORY")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyCredentialsDirectoryWarning(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
|
||||||
|
logger := &testutil.CaptureLogger{}
|
||||||
|
plugin := &Systemd{
|
||||||
|
Path: "testdata",
|
||||||
|
Log: logger}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
actual := logger.Warnings()
|
||||||
|
require.Len(t, actual, 1)
|
||||||
|
require.Contains(t, actual[0], "CREDENTIALS_DIRECTORY environment variable undefined")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathNonExistentExplicit(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
plugin := &Systemd{
|
||||||
|
Path: "non/existent/path",
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.ErrorContains(t, plugin.Init(), "accessing credentials directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathNonExistentImplicit(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "non/existent/path")
|
||||||
|
|
||||||
|
plugin := &Systemd{
|
||||||
|
Log: testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.ErrorContains(t, plugin.Init(), "accessing credentials directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInit(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetNotAvailable(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// Try to Store the secrets, which this plugin should not let
|
||||||
|
require.ErrorContains(t, plugin.Set("foo", "bar"), "secret-store does not support creating secrets")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListGet(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
// secret files name and their content to compare under the `testdata` directory
|
||||||
|
secrets := map[string]string{
|
||||||
|
"secret-file-1": "IWontTell",
|
||||||
|
"secret_file_2": "SuperDuperSecret!23",
|
||||||
|
"secretFile": "foobar",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the plugin
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// List the Secrets
|
||||||
|
keys, err := plugin.List()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, keys, len(secrets))
|
||||||
|
// check if the returned array from List() is the same
|
||||||
|
// as the name of secret files
|
||||||
|
for secretFileName := range secrets {
|
||||||
|
require.Contains(t, keys, secretFileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the secrets
|
||||||
|
for _, k := range keys {
|
||||||
|
value, err := plugin.Get(k)
|
||||||
|
require.NoError(t, err)
|
||||||
|
v, found := secrets[k]
|
||||||
|
require.Truef(t, found, "unexpected secret requested that was not found: %q", k)
|
||||||
|
require.Equal(t, v, string(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
// Secret Value Name to Resolve
|
||||||
|
secretFileName := "secret-file-1"
|
||||||
|
// Secret Value to Resolve To
|
||||||
|
secretVal := "IWontTell"
|
||||||
|
|
||||||
|
// Initialize the plugin
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// Get the resolver
|
||||||
|
resolver, err := plugin.GetResolver(secretFileName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, resolver)
|
||||||
|
s, dynamic, err := resolver()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, dynamic)
|
||||||
|
require.Equal(t, secretVal, string(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverInvalid(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
// Initialize the plugin
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// Get the resolver
|
||||||
|
resolver, err := plugin.GetResolver("foo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, resolver)
|
||||||
|
_, _, err = resolver()
|
||||||
|
require.ErrorContains(t, err, "cannot read the secret's value:")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNonExistant(t *testing.T) {
|
||||||
|
getSystemdVersion = getSystemdVersionMin
|
||||||
|
t.Setenv("CREDENTIALS_DIRECTORY", "testdata")
|
||||||
|
|
||||||
|
// Initialize the plugin
|
||||||
|
plugin := &Systemd{Log: testutil.Logger{}}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
|
||||||
|
// Get the resolver
|
||||||
|
_, err := plugin.Get("foo")
|
||||||
|
require.ErrorContains(t, err, "cannot read the secret's value:")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
IWontTell
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
foobar
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
SuperDuperSecret!23
|
||||||
|
|
@ -8,6 +8,7 @@ Wants=network-online.target
|
||||||
Type=notify
|
Type=notify
|
||||||
EnvironmentFile=-/etc/default/telegraf
|
EnvironmentFile=-/etc/default/telegraf
|
||||||
User=telegraf
|
User=telegraf
|
||||||
|
ImportCredential=telegraf.*
|
||||||
ExecStart=/usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d $TELEGRAF_OPTS
|
ExecStart=/usr/bin/telegraf -config /etc/telegraf/telegraf.conf -config-directory /etc/telegraf/telegraf.d $TELEGRAF_OPTS
|
||||||
ExecReload=/bin/kill -HUP $MAINPID
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
@ -15,6 +16,7 @@ RestartForceExitStatus=SIGPIPE
|
||||||
KillMode=mixed
|
KillMode=mixed
|
||||||
TimeoutStopSec=5
|
TimeoutStopSec=5
|
||||||
LimitMEMLOCK=8M:8M
|
LimitMEMLOCK=8M:8M
|
||||||
|
PrivateMounts=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue