2015-04-24 17:31:57 +00:00
package cert
import (
2018-01-08 18:31:38 +00:00
"context"
2016-02-29 15:40:15 +00:00
"crypto/x509"
2015-10-06 01:10:20 +00:00
"fmt"
2019-02-01 16:23:40 +00:00
"strings"
2015-04-24 19:58:39 +00:00
"time"
2015-04-24 17:31:57 +00:00
2019-01-09 00:48:57 +00:00
sockaddr "github.com/hashicorp/go-sockaddr"
2019-04-13 07:44:06 +00:00
"github.com/hashicorp/vault/sdk/framework"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/helper/parseutil"
2019-04-12 22:08:46 +00:00
"github.com/hashicorp/vault/sdk/helper/policyutil"
2019-07-01 20:31:37 +00:00
"github.com/hashicorp/vault/sdk/helper/tokenutil"
2019-04-12 21:54:35 +00:00
"github.com/hashicorp/vault/sdk/logical"
2015-04-24 17:31:57 +00:00
)
2016-03-15 18:07:40 +00:00
func pathListCerts ( b * backend ) * framework . Path {
return & framework . Path {
Pattern : "certs/?" ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . ListOperation : b . pathCertList ,
} ,
HelpSynopsis : pathCertHelpSyn ,
HelpDescription : pathCertHelpDesc ,
}
}
2015-04-24 17:31:57 +00:00
func pathCerts ( b * backend ) * framework . Path {
2019-07-01 20:31:37 +00:00
p := & framework . Path {
2015-08-21 07:56:13 +00:00
Pattern : "certs/" + framework . GenericNameRegex ( "name" ) ,
2015-04-24 17:31:57 +00:00
Fields : map [ string ] * framework . FieldSchema {
"name" : & framework . FieldSchema {
2019-02-01 16:23:40 +00:00
Type : framework . TypeString ,
2015-04-24 17:31:57 +00:00
Description : "The name of the certificate" ,
} ,
"certificate" : & framework . FieldSchema {
2015-09-18 18:01:28 +00:00
Type : framework . TypeString ,
Description : ` The public certificate that should be trusted .
Must be x509 PEM encoded . ` ,
2015-04-24 17:31:57 +00:00
} ,
2017-04-30 15:37:10 +00:00
"allowed_names" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated list of names .
2018-05-25 14:34:46 +00:00
At least one must exist in either the Common Name or SANs . Supports globbing .
This parameter is deprecated , please use allowed_common_names , allowed_dns_sans ,
allowed_email_sans , allowed_uri_sans . ` ,
} ,
"allowed_common_names" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated list of names .
At least one must exist in the Common Name . Supports globbing . ` ,
} ,
"allowed_dns_sans" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated list of DNS names .
At least one must exist in the SANs . Supports globbing . ` ,
} ,
"allowed_email_sans" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated list of Email Addresses .
At least one must exist in the SANs . Supports globbing . ` ,
} ,
"allowed_uri_sans" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated list of URIs .
At least one must exist in the SANs . Supports globbing . ` ,
2017-04-30 15:37:10 +00:00
} ,
2018-09-28 00:04:55 +00:00
"allowed_organizational_units" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated list of Organizational Units names .
At least one must exist in the OU field . ` ,
} ,
2017-12-18 17:53:44 +00:00
"required_extensions" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
Description : ` A comma - separated string or array of extensions
formatted as "oid:value" . Expects the extension value to be some type of ASN1 encoded string .
All values much match . Supports globbing on "value" . ` ,
} ,
2015-04-24 17:31:57 +00:00
"display_name" : & framework . FieldSchema {
2015-09-18 18:01:28 +00:00
Type : framework . TypeString ,
Description : ` The display name to use for clients using this
certificate . ` ,
2015-04-24 17:31:57 +00:00
} ,
2019-02-01 16:23:40 +00:00
"policies" : & framework . FieldSchema {
Type : framework . TypeCommaStringSlice ,
2019-07-01 20:31:37 +00:00
Description : tokenutil . DeprecationText ( "token_policies" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
2015-04-24 19:58:39 +00:00
"lease" : & framework . FieldSchema {
2019-07-01 20:31:37 +00:00
Type : framework . TypeInt ,
Description : tokenutil . DeprecationText ( "token_ttl" ) ,
Deprecated : true ,
2015-09-18 18:01:28 +00:00
} ,
2019-02-01 16:23:40 +00:00
"ttl" : & framework . FieldSchema {
2019-07-01 20:31:37 +00:00
Type : framework . TypeDurationSecond ,
Description : tokenutil . DeprecationText ( "token_ttl" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
"max_ttl" : & framework . FieldSchema {
2019-07-01 20:31:37 +00:00
Type : framework . TypeDurationSecond ,
Description : tokenutil . DeprecationText ( "token_max_ttl" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
"period" : & framework . FieldSchema {
2019-07-01 20:31:37 +00:00
Type : framework . TypeDurationSecond ,
Description : tokenutil . DeprecationText ( "token_period" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
2019-07-01 20:31:37 +00:00
2019-02-01 16:23:40 +00:00
"bound_cidrs" : & framework . FieldSchema {
2019-07-01 20:31:37 +00:00
Type : framework . TypeCommaStringSlice ,
Description : tokenutil . DeprecationText ( "token_bound_cidrs" ) ,
Deprecated : true ,
2019-02-01 16:23:40 +00:00
} ,
2015-04-24 17:31:57 +00:00
} ,
Callbacks : map [ logical . Operation ] framework . OperationFunc {
logical . DeleteOperation : b . pathCertDelete ,
logical . ReadOperation : b . pathCertRead ,
2016-02-29 15:40:15 +00:00
logical . UpdateOperation : b . pathCertWrite ,
2015-04-24 17:31:57 +00:00
} ,
HelpSynopsis : pathCertHelpSyn ,
HelpDescription : pathCertHelpDesc ,
}
2019-07-01 20:31:37 +00:00
tokenutil . AddTokenFields ( p . Fields )
return p
2015-04-24 17:31:57 +00:00
}
2018-01-19 06:44:44 +00:00
func ( b * backend ) Cert ( ctx context . Context , s logical . Storage , n string ) ( * CertEntry , error ) {
2019-02-01 16:23:40 +00:00
entry , err := s . Get ( ctx , "cert/" + strings . ToLower ( n ) )
2015-04-24 17:31:57 +00:00
if err != nil {
return nil , err
}
if entry == nil {
return nil , nil
}
var result CertEntry
if err := entry . DecodeJSON ( & result ) ; err != nil {
return nil , err
}
2019-07-01 20:31:37 +00:00
if result . TokenTTL == 0 && result . TTL > 0 {
result . TokenTTL = result . TTL
}
if result . TokenMaxTTL == 0 && result . MaxTTL > 0 {
result . TokenMaxTTL = result . MaxTTL
}
if result . TokenPeriod == 0 && result . Period > 0 {
result . TokenPeriod = result . Period
}
if len ( result . TokenPolicies ) == 0 && len ( result . Policies ) > 0 {
result . TokenPolicies = result . Policies
}
if len ( result . TokenBoundCIDRs ) == 0 && len ( result . BoundCIDRs ) > 0 {
result . TokenBoundCIDRs = result . BoundCIDRs
}
2015-04-24 17:31:57 +00:00
return & result , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathCertDelete ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-02-01 16:23:40 +00:00
err := req . Storage . Delete ( ctx , "cert/" + strings . ToLower ( d . Get ( "name" ) . ( string ) ) )
2015-04-24 17:31:57 +00:00
if err != nil {
return nil , err
}
return nil , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathCertList ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2018-01-19 06:44:44 +00:00
certs , err := req . Storage . List ( ctx , "cert/" )
2016-03-15 18:07:40 +00:00
if err != nil {
return nil , err
}
return logical . ListResponse ( certs ) , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathCertRead ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-02-01 16:23:40 +00:00
cert , err := b . Cert ( ctx , req . Storage , strings . ToLower ( d . Get ( "name" ) . ( string ) ) )
2015-04-24 17:31:57 +00:00
if err != nil {
return nil , err
}
if cert == nil {
return nil , nil
}
2019-07-01 20:31:37 +00:00
data := map [ string ] interface { } {
"certificate" : cert . Certificate ,
"display_name" : cert . DisplayName ,
"allowed_names" : cert . AllowedNames ,
"allowed_common_names" : cert . AllowedCommonNames ,
"allowed_dns_sans" : cert . AllowedDNSSANs ,
"allowed_email_sans" : cert . AllowedEmailSANs ,
"allowed_uri_sans" : cert . AllowedURISANs ,
"allowed_organizational_units" : cert . AllowedOrganizationalUnits ,
"required_extensions" : cert . RequiredExtensions ,
}
cert . PopulateTokenData ( data )
if cert . TTL > 0 {
data [ "ttl" ] = int64 ( cert . TTL . Seconds ( ) )
}
if cert . MaxTTL > 0 {
data [ "max_ttl" ] = int64 ( cert . MaxTTL . Seconds ( ) )
}
if cert . Period > 0 {
data [ "period" ] = int64 ( cert . Period . Seconds ( ) )
}
if len ( cert . Policies ) > 0 {
data [ "policies" ] = data [ "token_policies" ]
}
if len ( cert . BoundCIDRs ) > 0 {
data [ "bound_cidrs" ] = data [ "token_bound_cidrs" ]
}
2015-04-24 17:31:57 +00:00
return & logical . Response {
2019-07-01 20:31:37 +00:00
Data : data ,
2015-04-24 17:31:57 +00:00
} , nil
}
2018-01-08 18:31:38 +00:00
func ( b * backend ) pathCertWrite ( ctx context . Context , req * logical . Request , d * framework . FieldData ) ( * logical . Response , error ) {
2019-02-01 16:23:40 +00:00
name := strings . ToLower ( d . Get ( "name" ) . ( string ) )
2015-04-24 17:31:57 +00:00
2019-07-01 20:31:37 +00:00
cert , err := b . Cert ( ctx , req . Storage , name )
if err != nil {
return nil , err
2019-02-01 16:23:40 +00:00
}
2019-07-01 20:31:37 +00:00
if cert == nil {
cert = & CertEntry {
Name : name ,
}
2017-12-18 20:29:45 +00:00
}
2019-07-01 20:31:37 +00:00
// Get non tokenutil fields
if certificateRaw , ok := d . GetOk ( "certificate" ) ; ok {
cert . Certificate = certificateRaw . ( string )
2017-12-18 20:29:45 +00:00
}
2019-07-01 20:31:37 +00:00
if displayNameRaw , ok := d . GetOk ( "display_name" ) ; ok {
cert . DisplayName = displayNameRaw . ( string )
}
if allowedNamesRaw , ok := d . GetOk ( "allowed_names" ) ; ok {
cert . AllowedNames = allowedNamesRaw . ( [ ] string )
}
if allowedCommonNamesRaw , ok := d . GetOk ( "allowed_common_names" ) ; ok {
cert . AllowedCommonNames = allowedCommonNamesRaw . ( [ ] string )
}
if allowedDNSSANsRaw , ok := d . GetOk ( "allowed_dns_sans" ) ; ok {
cert . AllowedDNSSANs = allowedDNSSANsRaw . ( [ ] string )
}
if allowedEmailSANsRaw , ok := d . GetOk ( "allowed_email_sans" ) ; ok {
cert . AllowedEmailSANs = allowedEmailSANsRaw . ( [ ] string )
}
if allowedURISANsRaw , ok := d . GetOk ( "allowed_uri_sans" ) ; ok {
cert . AllowedURISANs = allowedURISANsRaw . ( [ ] string )
}
if allowedOrganizationalUnitsRaw , ok := d . GetOk ( "allowed_organizational_units" ) ; ok {
cert . AllowedOrganizationalUnits = allowedOrganizationalUnitsRaw . ( [ ] string )
}
if requiredExtensionsRaw , ok := d . GetOk ( "required_extensions" ) ; ok {
cert . RequiredExtensions = requiredExtensionsRaw . ( [ ] string )
2019-02-01 16:23:40 +00:00
}
2019-07-01 20:31:37 +00:00
// Get tokenutil fields
if err := cert . ParseTokenFields ( req , d ) ; err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
2017-12-18 20:29:45 +00:00
}
2019-07-01 20:31:37 +00:00
// Handle upgrade cases
{
policiesRaw , ok := d . GetOk ( "token_policies" )
if ! ok {
policiesRaw , ok = d . GetOk ( "policies" )
if ok {
cert . Policies = policyutil . ParsePolicies ( policiesRaw )
cert . TokenPolicies = cert . Policies
}
} else {
_ , ok = d . GetOk ( "policies" )
if ok {
cert . Policies = cert . TokenPolicies
} else {
cert . Policies = nil
}
}
ttlRaw , ok := d . GetOk ( "token_ttl" )
if ! ok {
ttlRaw , ok = d . GetOk ( "ttl" )
if ! ok {
ttlRaw , ok = d . GetOk ( "lease" )
}
if ok {
cert . TTL = time . Duration ( ttlRaw . ( int ) ) * time . Second
cert . TokenTTL = cert . TTL
}
} else {
_ , ok = d . GetOk ( "ttl" )
if ok {
cert . TTL = cert . TokenTTL
} else {
cert . TTL = 0
}
}
maxTTLRaw , ok := d . GetOk ( "token_max_ttl" )
if ! ok {
maxTTLRaw , ok = d . GetOk ( "max_ttl" )
if ok {
cert . MaxTTL = time . Duration ( maxTTLRaw . ( int ) ) * time . Second
cert . TokenMaxTTL = cert . MaxTTL
}
} else {
_ , ok = d . GetOk ( "max_ttl" )
if ok {
cert . MaxTTL = cert . TokenMaxTTL
} else {
cert . MaxTTL = 0
}
}
periodRaw , ok := d . GetOk ( "token_period" )
if ! ok {
periodRaw , ok = d . GetOk ( "period" )
if ok {
cert . Period = time . Duration ( periodRaw . ( int ) ) * time . Second
cert . TokenPeriod = cert . Period
}
} else {
_ , ok = d . GetOk ( "period" )
if ok {
cert . Period = cert . TokenPeriod
} else {
cert . Period = 0
}
}
boundCIDRsRaw , ok := d . GetOk ( "token_bound_cidrs" )
if ! ok {
boundCIDRsRaw , ok = d . GetOk ( "bound_cidrs" )
if ok {
boundCIDRs , err := parseutil . ParseAddrs ( boundCIDRsRaw )
if err != nil {
return logical . ErrorResponse ( err . Error ( ) ) , logical . ErrInvalidRequest
}
cert . BoundCIDRs = boundCIDRs
cert . TokenBoundCIDRs = cert . BoundCIDRs
}
} else {
_ , ok = d . GetOk ( "bound_cidrs" )
if ok {
cert . BoundCIDRs = cert . TokenBoundCIDRs
} else {
cert . BoundCIDRs = nil
}
}
2019-02-01 16:23:40 +00:00
}
2019-07-01 20:31:37 +00:00
var resp logical . Response
systemDefaultTTL := b . System ( ) . DefaultLeaseTTL ( )
if cert . TokenTTL > systemDefaultTTL {
resp . AddWarning ( fmt . Sprintf ( "Given ttl of %d seconds is greater than current mount/system default of %d seconds" , cert . TokenTTL / time . Second , systemDefaultTTL / time . Second ) )
}
systemMaxTTL := b . System ( ) . MaxLeaseTTL ( )
if cert . TokenMaxTTL > systemMaxTTL {
resp . AddWarning ( fmt . Sprintf ( "Given max_ttl of %d seconds is greater than current mount/system default of %d seconds" , cert . TokenMaxTTL / time . Second , systemMaxTTL / time . Second ) )
}
if cert . TokenMaxTTL != 0 && cert . TokenTTL > cert . TokenMaxTTL {
return logical . ErrorResponse ( "ttl should be shorter than max_ttl" ) , nil
}
if cert . TokenPeriod > systemMaxTTL {
resp . AddWarning ( fmt . Sprintf ( "Given period of %d seconds is greater than the backend's maximum TTL of %d seconds" , cert . TokenPeriod / time . Second , systemMaxTTL / time . Second ) )
2017-12-18 20:29:45 +00:00
}
2015-04-24 17:52:17 +00:00
// Default the display name to the certificate name if not given
2019-07-01 20:31:37 +00:00
if cert . DisplayName == "" {
cert . DisplayName = name
2015-04-24 17:52:17 +00:00
}
2019-07-01 20:31:37 +00:00
parsed := parsePEM ( [ ] byte ( cert . Certificate ) )
2015-04-24 17:39:44 +00:00
if len ( parsed ) == 0 {
return logical . ErrorResponse ( "failed to parse certificate" ) , nil
}
2016-02-29 15:40:15 +00:00
// If the certificate is not a CA cert, then ensure that x509.ExtKeyUsageClientAuth is set
2016-02-29 23:29:41 +00:00
if ! parsed [ 0 ] . IsCA && parsed [ 0 ] . ExtKeyUsage != nil {
2016-02-29 21:02:19 +00:00
var clientAuth bool
2016-02-29 15:40:15 +00:00
for _ , usage := range parsed [ 0 ] . ExtKeyUsage {
2016-03-01 15:24:22 +00:00
if usage == x509 . ExtKeyUsageClientAuth || usage == x509 . ExtKeyUsageAny {
2016-02-29 15:40:15 +00:00
clientAuth = true
2016-03-01 15:24:22 +00:00
break
2016-02-29 15:40:15 +00:00
}
}
if ! clientAuth {
2016-02-29 23:29:41 +00:00
return logical . ErrorResponse ( "non-CA certificates should have TLS client authentication set as an extended key usage" ) , nil
2016-02-29 15:40:15 +00:00
}
}
2015-04-24 17:31:57 +00:00
// Store it
2019-07-01 20:31:37 +00:00
entry , err := logical . StorageEntryJSON ( "cert/" + name , cert )
2015-04-24 17:31:57 +00:00
if err != nil {
return nil , err
}
2018-01-19 06:44:44 +00:00
if err := req . Storage . Put ( ctx , entry ) ; err != nil {
2015-04-24 17:31:57 +00:00
return nil , err
}
2017-12-18 20:29:45 +00:00
if len ( resp . Warnings ) == 0 {
return nil , nil
}
return & resp , nil
2015-04-24 17:31:57 +00:00
}
type CertEntry struct {
2019-07-01 20:31:37 +00:00
tokenutil . TokenParams
2018-09-28 00:04:55 +00:00
Name string
Certificate string
DisplayName string
2019-02-01 16:23:40 +00:00
Policies [ ] string
TTL time . Duration
MaxTTL time . Duration
Period time . Duration
2018-09-28 00:04:55 +00:00
AllowedNames [ ] string
AllowedCommonNames [ ] string
AllowedDNSSANs [ ] string
AllowedEmailSANs [ ] string
AllowedURISANs [ ] string
AllowedOrganizationalUnits [ ] string
RequiredExtensions [ ] string
2019-02-01 16:23:40 +00:00
BoundCIDRs [ ] * sockaddr . SockAddrMarshaler
2015-04-24 17:31:57 +00:00
}
const pathCertHelpSyn = `
Manage trusted certificates used for authentication .
`
const pathCertHelpDesc = `
This endpoint allows you to create , read , update , and delete trusted certificates
that are allowed to authenticate .
Deleting a certificate will not revoke auth for prior authenticated connections .
To do this , do a revoke on "login" . If you don ' t need to revoke login immediately ,
then the next renew will cause the lease to expire .
`