217 lines
5.1 KiB
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
|
|
}
|