Mainflux.mainflux/certs/pki/vault.go

217 lines
5.1 KiB
Go

// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package pki wraps vault client
package pki
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/hashicorp/vault/api"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mitchellh/mapstructure"
)
const (
issue = "issue"
cert = "cert"
revoke = "revoke"
apiVer = "v1"
)
var (
// ErrMissingCACertificate indicates missing CA certificate
ErrMissingCACertificate = errors.New("missing CA certificate for certificate signing")
// ErrFailedCertCreation indicates failed to certificate creation
ErrFailedCertCreation = errors.New("failed to create client certificate")
// ErrFailedCertRevocation indicates failed certificate revocation
ErrFailedCertRevocation = errors.New("failed to revoke certificate")
errFailedVaultCertIssue = errors.New("failed to issue vault certificate")
errFailedVaultRead = errors.New("failed to read vault certificate")
errFailedCertDecoding = errors.New("failed to decode response from vault service")
)
type Cert struct {
ClientCert string `json:"client_cert" mapstructure:"certificate"`
IssuingCA string `json:"issuing_ca" mapstructure:"issuing_ca"`
CAChain []string `json:"ca_chain" mapstructure:"ca_chain"`
ClientKey string `json:"client_key" mapstructure:"private_key"`
PrivateKeyType string `json:"private_key_type" mapstructure:"private_key_type"`
Serial string `json:"serial" mapstructure:"serial_number"`
Expire time.Time `json:"expire" mapstructure:"-"`
}
// Agent represents the Vault PKI interface.
type Agent interface {
// IssueCert issues certificate on PKI
IssueCert(cn string, ttl, keyType string, keyBits int) (Cert, error)
// Read retrieves certificate from PKI
Read(serial string) (Cert, error)
// Revoke revokes certificate from PKI
Revoke(serial string) (time.Time, error)
}
type pkiAgent struct {
token string
path string
role string
host string
issueURL string
readURL string
revokeURL string
client *api.Client
}
type certReq struct {
CommonName string `json:"common_name"`
TTL string `json:"ttl"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
}
type certRevokeReq struct {
SerialNumber string `json:"serial_number"`
}
// NewVaultClient instantiates a Vault client.
func NewVaultClient(token, host, path, role string) (Agent, error) {
conf := &api.Config{
Address: host,
}
client, err := api.NewClient(conf)
if err != nil {
return nil, err
}
client.SetToken(token)
p := pkiAgent{
token: token,
host: host,
role: role,
path: path,
client: client,
issueURL: "/" + apiVer + "/" + path + "/" + issue + "/" + role,
readURL: "/" + apiVer + "/" + path + "/" + cert + "/",
revokeURL: "/" + apiVer + "/" + path + "/" + revoke,
}
return &p, nil
}
func (p *pkiAgent) IssueCert(cn string, ttl, keyType string, keyBits int) (Cert, error) {
cReq := certReq{
CommonName: cn,
TTL: ttl,
KeyBits: keyBits,
KeyType: keyType,
}
r := p.client.NewRequest("POST", p.issueURL)
if err := r.SetJSONBody(cReq); err != nil {
return Cert{}, err
}
resp, err := p.client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return Cert{}, err
}
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Cert{}, err
}
return Cert{}, errors.Wrap(errFailedVaultCertIssue, err)
}
s, err := api.ParseSecret(resp.Body)
if err != nil {
return Cert{}, err
}
cert := Cert{}
if err = mapstructure.Decode(s.Data, &cert); err != nil {
return Cert{}, errors.Wrap(errFailedCertDecoding, err)
}
return cert, nil
}
func (p *pkiAgent) Read(serial string) (Cert, error) {
r := p.client.NewRequest("GET", p.readURL+"/"+serial)
resp, err := p.client.RawRequest(r)
if err != nil {
return Cert{}, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Cert{}, err
}
return Cert{}, errors.Wrap(errFailedVaultRead, err)
}
s, err := api.ParseSecret(resp.Body)
if err != nil {
return Cert{}, err
}
cert := Cert{}
if err = mapstructure.Decode(s.Data, &cert); err != nil {
return Cert{}, errors.Wrap(errFailedCertDecoding, err)
}
return cert, nil
}
func (p *pkiAgent) Revoke(serial string) (time.Time, error) {
cReq := certRevokeReq{
SerialNumber: serial,
}
r := p.client.NewRequest("POST", p.revokeURL)
if err := r.SetJSONBody(cReq); err != nil {
return time.Time{}, err
}
resp, err := p.client.RawRequest(r)
if err != nil {
return time.Time{}, err
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
_, err := ioutil.ReadAll(resp.Body)
if err != nil {
return time.Time{}, err
}
return time.Time{}, errors.Wrap(errFailedVaultCertIssue, err)
}
s, err := api.ParseSecret(resp.Body)
if err != nil {
return time.Time{}, err
}
rev, err := s.Data["revocation_time"].(json.Number).Float64()
if err != nil {
return time.Time{}, err
}
return time.Unix(0, int64(rev)*int64(time.Millisecond)), nil
}