Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NOISSUE - Vault operations with app role authentication #2084

Merged
merged 40 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
6d64394
add: modified as per server need
arvindh123 Feb 7, 2024
26f35d2
ignore letsencrypt dir
arvindh123 Feb 7, 2024
b54beaf
add: certbot and env variables for certificate
arvindh123 Feb 7, 2024
0c23330
fix: path
arvindh123 Feb 7, 2024
40fdbdf
fix: api sub route path
arvindh123 Feb 7, 2024
c3f1b1f
fix: api route
arvindh123 Feb 7, 2024
a38f44a
add: add support for vault open source and hcp vault
arvindh123 Feb 14, 2024
8096f26
fix: vault create approle script
arvindh123 Feb 14, 2024
ddd72d3
fix: vault create approle script
arvindh123 Feb 14, 2024
9d3b674
fix: vault create approle script
arvindh123 Feb 14, 2024
c163827
fix: vault create approle script
arvindh123 Feb 14, 2024
b6224e6
remove env var
arvindh123 Feb 14, 2024
7f13ff9
Revert "fix: api route"
arvindh123 Feb 19, 2024
6b0bb75
vault_certs
arvindh123 Feb 19, 2024
cb92b4c
Revert "fix: api sub route path"
arvindh123 Feb 19, 2024
f4e0858
Revert "fix: path"
arvindh123 Feb 19, 2024
d9990db
Revert "add: certbot and env variables for certificate"
arvindh123 Feb 19, 2024
1fad340
Revert "ignore letsencrypt dir"
arvindh123 Feb 19, 2024
a692e41
Revert "add: modified as per server need"
arvindh123 Feb 19, 2024
2e88faa
revert back
arvindh123 Feb 19, 2024
3a31d48
remove new vault dir
arvindh123 Feb 19, 2024
e5d39ec
update vault scritps and env vars
arvindh123 Feb 19, 2024
95a1188
update nginx
arvindh123 Feb 19, 2024
a85a7bc
update nginx
arvindh123 Feb 19, 2024
cf718d6
update certs readme
arvindh123 Feb 19, 2024
4b7d4fe
update vault scripts
arvindh123 Feb 19, 2024
38a93ee
update certs env vars
arvindh123 Feb 19, 2024
d642845
fix add new line gitignore
arvindh123 Feb 19, 2024
16617c5
fix vault readme
arvindh123 Feb 19, 2024
3c777c4
fix vault readme
arvindh123 Feb 19, 2024
5650ae7
fix certs compose & vault script
arvindh123 Feb 19, 2024
f99ec6f
fix golangci-lint
arvindh123 Feb 19, 2024
0d53381
fix golangci-lint
arvindh123 Feb 19, 2024
a643084
fix vault script
arvindh123 Feb 19, 2024
bf32e4c
fix spacing in vault scripts
arvindh123 Feb 20, 2024
7d235be
change hypen to underscore in vault script
arvindh123 Feb 20, 2024
c052f3c
change hypen to underscore in vault script
arvindh123 Feb 20, 2024
38504a9
fix nginx server_name env variable
arvindh123 Feb 20, 2024
295e9f8
remove vault dummy token from .env
arvindh123 Feb 20, 2024
3e77076
update vault scripts
arvindh123 Feb 20, 2024
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
77 changes: 41 additions & 36 deletions certs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,38 +30,41 @@ curl -s -S -X DELETE http://localhost:9019/certs/revoke -H "Authorization: Beare

The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.

| Variable | Description | Default |
| ------------------------- | --------------------------------------------------------------------------- | ----------------------------------- |
| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info |
| MG_CERTS_HTTP_HOST | Service Certs host | "" |
| MG_CERTS_HTTP_PORT | Service Certs port | 9019 |
| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" |
| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" |
| MG_AUTH_GRPC_URL | Auth service gRPC URL | <localhost:8181> |
| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s |
| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" |
| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" |
| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" |
| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt |
| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key |
| MG_CERTS_VAULT_HOST | Vault host | "" |
| MG_VAULT_PKI_INT_PATH | Vault PKI intermediate path | pki_int |
| MG_VAULT_CA_ROLE_NAME | Vault PKI role name | magistrala |
| MG_VAULT_TOKEN | Vault token | "" |
| MG_CERTS_DB_HOST | Database host | localhost |
| MG_CERTS_DB_PORT | Database port | 5432 |
| MG_CERTS_DB_PASS | Database password | magistrala |
| MG_CERTS_DB_USER | Database user | magistrala |
| MG_CERTS_DB_NAME | Database name | certs |
| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable |
| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" |
| MG_CERTS_DB_SSL_KEY | Database SSL key | "" |
| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" |
| MG_THINGS_URL | Things service URL | <localhost:9000> |
| MG_JAEGER_URL | Jaeger server URL | <http://localhost:14268/api/traces> |
| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_CERTS_INSTANCE_ID | Service instance ID | "" |

| Variable | Description | Default |
| :---------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| MG_CERTS_LOG_LEVEL | Log level for the Certs (debug, info, warn, error) | info |
| MG_CERTS_HTTP_HOST | Service Certs host | "" |
| MG_CERTS_HTTP_PORT | Service Certs port | 9019 |
| MG_CERTS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" |
| MG_CERTS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" |
| MG_AUTH_GRPC_URL | Auth service gRPC URL | [localhost:8181](localhost:8181) |
| MG_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s |
| MG_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded auth service gRPC client certificate file | "" |
| MG_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded auth service gRPC client key file | "" |
| MG_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded auth server gRPC server trusted CA certificate file | "" |
| MG_CERTS_SIGN_CA_PATH | Path to the PEM encoded CA certificate file | ca.crt |
| MG_CERTS_SIGN_CA_KEY_PATH | Path to the PEM encoded CA key file | ca.key |
| MG_CERTS_VAULT_HOST | Vault host | http://vault:8200 |
| MG_CERTS_VAULT_NAMESPACE | Vault namespace in which pki is present | magistrala |
| MG_CERTS_VAULT_APPROLE_ROLEID | Vault AppRole auth RoleID | magistrala |
| MG_CERTS_VAULT_APPROLE_SECRET | Vault AppRole auth Secret | magistrala |
| MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH | Vault PKI path for issuing Things Certificates | pki_int |
| MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME | Vault PKI Role Name for issuing Things Certificates | magistrala_things_certs |
| MG_CERTS_DB_HOST | Database host | localhost |
| MG_CERTS_DB_PORT | Database port | 5432 |
| MG_CERTS_DB_PASS | Database password | magistrala |
| MG_CERTS_DB_USER | Database user | magistrala |
| MG_CERTS_DB_NAME | Database name | certs |
| MG_CERTS_DB_SSL_MODE | Database SSL mode | disable |
| MG_CERTS_DB_SSL_CERT | Database SSL certificate | "" |
| MG_CERTS_DB_SSL_KEY | Database SSL key | "" |
| MG_CERTS_DB_SSL_ROOT_CERT | Database SSL root certificate | "" |
| MG_THINGS_URL | Things service URL | [localhost:9000](localhost:9000) |
| MG_JAEGER_URL | Jaeger server URL | [http://localhost:14268/api/traces](http://localhost:14268/api/traces) |
| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
| MG_CERTS_INSTANCE_ID | Service instance ID | "" |

## Deployment

Expand Down Expand Up @@ -95,10 +98,12 @@ MG_AUTH_GRPC_CLIENT_KEY="" \
MG_AUTH_GRPC_SERVER_CERTS="" \
MG_CERTS_SIGN_CA_PATH=ca.crt \
MG_CERTS_SIGN_CA_KEY_PATH=ca.key \
MG_CERTS_VAULT_HOST="" \
MG_VAULT_PKI_INT_PATH=pki_int \
MG_VAULT_CA_ROLE_NAME=magistrala \
MG_VAULT_TOKEN="" \
MG_CERTS_VAULT_HOST=http://vault:8200 \
MG_CERTS_VAULT_NAMESPACE=magistrala \
MG_CERTS_VAULT_APPROLE_ROLEID=magistrala \
MG_CERTS_VAULT_APPROLE_SECRET=magistrala \
MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH=pki_int \
MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME=magistrala_things_certs \
MG_CERTS_DB_HOST=localhost \
MG_CERTS_DB_PORT=5432 \
MG_CERTS_DB_PASS=magistrala \
Expand Down
5 changes: 5 additions & 0 deletions certs/mocks/pki.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mocks
import (
"bufio"
"bytes"
"context"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
Expand Down Expand Up @@ -160,6 +161,10 @@ func (a *agent) Revoke(serial string) (time.Time, error) {
return time.Now(), nil
}

func (a *agent) LoginAndRenew(ctx context.Context) error {
return nil
}

func publicKey(priv interface{}) (interface{}, error) {
if priv == nil {
return nil, errPrivateKeyEmpty
Expand Down
109 changes: 105 additions & 4 deletions certs/pki/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
package pki

import (
"context"
"encoding/json"
"log/slog"
"time"

"github.com/absmach/magistrala/pkg/errors"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/api/auth/approle"
"github.com/mitchellh/mapstructure"
)

Expand All @@ -30,6 +33,13 @@ var (
ErrFailedCertRevocation = errors.New("failed to revoke certificate")

errFailedCertDecoding = errors.New("failed to decode response from vault service")
errFailedToLogin = errors.New("failed to login to Vault")
errFailedAppRole = errors.New("failed to create vault new app role")
errNoAuthInfo = errors.New("no auth information from Vault")
errNonRenewal = errors.New("token is not configured to be renewable")
errRenewWatcher = errors.New("unable to initialize new lifetime watcher for renewing auth token")
errFailedRenew = errors.New("failed to renew token")
errCouldNotRenew = errors.New("token can no longer be renewed")
)

type Cert struct {
Expand All @@ -52,17 +62,24 @@ type Agent interface {

// Revoke revokes certificate from PKI
Revoke(serial string) (time.Time, error)

// Login to PKI and renews token
LoginAndRenew(ctx context.Context) error
}

type pkiAgent struct {
token string
appRole string
appSecret string
namespace string
path string
role string
host string
issueURL string
readURL string
revokeURL string
client *api.Client
secret *api.Secret
logger *slog.Logger
}

type certReq struct {
Expand All @@ -75,21 +92,27 @@ type certRevokeReq struct {
}

// NewVaultClient instantiates a Vault client.
func NewVaultClient(token, host, path, role string) (Agent, error) {
func NewVaultClient(appRole, appSecret, host, namespace, path, role string, logger *slog.Logger) (Agent, error) {
conf := api.DefaultConfig()
conf.Address = host

client, err := api.NewClient(conf)
if err != nil {
return nil, err
}
client.SetToken(token)
if namespace != "" {
client.SetNamespace(namespace)
}

p := pkiAgent{
token: token,
appRole: appRole,
appSecret: appSecret,
host: host,
namespace: namespace,
role: role,
path: path,
client: client,
logger: logger,
issueURL: "/" + path + "/" + issue + "/" + role,
readURL: "/" + path + "/" + cert + "/",
revokeURL: "/" + path + "/" + revoke,
Expand Down Expand Up @@ -162,3 +185,81 @@ func (p *pkiAgent) Revoke(serial string) (time.Time, error) {

return time.Unix(0, int64(rev)*int64(time.Second)), nil
}

func (p *pkiAgent) LoginAndRenew(ctx context.Context) error {
for {
select {
case <-ctx.Done():
p.logger.Info("pki login and renew function stopping")
return nil
default:
err := p.login(ctx)
if err != nil {
p.logger.Info("unable to authenticate to Vault", slog.Any("error", err))
time.Sleep(5 * time.Second)
break
}
tokenErr := p.manageTokenLifecycle()
if tokenErr != nil {
p.logger.Info("unable to start managing token lifecycle", slog.Any("error", tokenErr))
time.Sleep(5 * time.Second)
}
}
}
}

func (p *pkiAgent) login(ctx context.Context) error {
secretID := &approle.SecretID{FromString: p.appSecret}

authMethod, err := approle.NewAppRoleAuth(
p.appRole,
secretID,
)
if err != nil {
return errors.Wrap(errFailedAppRole, err)
}
if p.namespace != "" {
p.client.SetNamespace(p.namespace)
}
secret, err := p.client.Auth().Login(ctx, authMethod)
if err != nil {
return errors.Wrap(errFailedToLogin, err)
}
if secret == nil {
return errNoAuthInfo
}
p.secret = secret
return nil
}

func (p *pkiAgent) manageTokenLifecycle() error {
renew := p.secret.Auth.Renewable
if !renew {
return errNonRenewal
}

watcher, err := p.client.NewLifetimeWatcher(&api.LifetimeWatcherInput{
Secret: p.secret,
Increment: 3600, // Requesting token for 3600s = 1h, If this is more than token_max_ttl, then response token will have token_max_ttl
})
if err != nil {
return errors.Wrap(errRenewWatcher, err)
}

go watcher.Start()
defer watcher.Stop()

for {
select {
case err := <-watcher.DoneCh():
if err != nil {
return errors.Wrap(errFailedRenew, err)
}
// This occurs once the token has reached max TTL or if token is disabled for renewal.
return errCouldNotRenew

case renewal := <-watcher.RenewCh():
p.logger.Info("Successfully renewed token", slog.Any("renewed_at", renewal.RenewedAt))
}
}
}
16 changes: 11 additions & 5 deletions cmd/certs/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,12 @@ type config struct {
SignCAKeyPath string `env:"MG_CERTS_SIGN_CA_KEY_PATH" envDefault:"ca.key"`

// 3rd party PKI API access settings
PkiHost string `env:"MG_CERTS_VAULT_HOST" envDefault:""`
PkiPath string `env:"MG_VAULT_PKI_INT_PATH" envDefault:"pki_int"`
PkiRole string `env:"MG_VAULT_CA_ROLE_NAME" envDefault:"magistrala"`
PkiToken string `env:"MG_VAULT_TOKEN" envDefault:""`
PkiHost string `env:"MG_CERTS_VAULT_HOST" envDefault:""`
PkiAppRoleID string `env:"MG_CERTS_VAULT_APPROLE_ROLEID" envDefault:""`
PkiAppSecret string `env:"MG_CERTS_VAULT_APPROLE_SECRET" envDefault:""`
PkiNamespace string `env:"MG_CERTS_VAULT_NAMESPACE" envDefault:""`
PkiPath string `env:"MG_CERTS_VAULT_THINGS_CERTS_PKI_PATH" envDefault:"pki_int"`
PkiRole string `env:"MG_CERTS_VAULT_THINGS_CERTS_PKI_ROLE_NAME" envDefault:"magistrala"`
}

func main() {
Expand Down Expand Up @@ -94,13 +96,17 @@ func main() {
return
}

pkiclient, err := vault.NewVaultClient(cfg.PkiToken, cfg.PkiHost, cfg.PkiPath, cfg.PkiRole)
pkiclient, err := vault.NewVaultClient(cfg.PkiAppRoleID, cfg.PkiAppSecret, cfg.PkiHost, cfg.PkiNamespace, cfg.PkiPath, cfg.PkiRole, logger)
if err != nil {
logger.Error("failed to configure client for PKI engine")
exitCode = 1
return
}

g.Go(func() error {
return pkiclient.LoginAndRenew(ctx)
})

dbConfig := pgclient.Config{Name: defDB}
if err := env.ParseWithOptions(&dbConfig, env.Options{Prefix: envPrefixDB}); err != nil {
logger.Error(err.Error())
Expand Down
Loading
Loading