1package cert 2 3import ( 4 "crypto/x509" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/vault/helper/policyutil" 10 "github.com/hashicorp/vault/logical" 11 "github.com/hashicorp/vault/logical/framework" 12) 13 14func pathListCerts(b *backend) *framework.Path { 15 return &framework.Path{ 16 Pattern: "certs/?", 17 18 Callbacks: map[logical.Operation]framework.OperationFunc{ 19 logical.ListOperation: b.pathCertList, 20 }, 21 22 HelpSynopsis: pathCertHelpSyn, 23 HelpDescription: pathCertHelpDesc, 24 } 25} 26 27func pathCerts(b *backend) *framework.Path { 28 return &framework.Path{ 29 Pattern: "certs/" + framework.GenericNameRegex("name"), 30 Fields: map[string]*framework.FieldSchema{ 31 "name": &framework.FieldSchema{ 32 Type: framework.TypeString, 33 Description: "The name of the certificate", 34 }, 35 36 "certificate": &framework.FieldSchema{ 37 Type: framework.TypeString, 38 Description: `The public certificate that should be trusted. 39Must be x509 PEM encoded.`, 40 }, 41 42 "display_name": &framework.FieldSchema{ 43 Type: framework.TypeString, 44 Description: `The display name to use for clients using this 45certificate.`, 46 }, 47 48 "policies": &framework.FieldSchema{ 49 Type: framework.TypeString, 50 Description: "Comma-seperated list of policies.", 51 }, 52 53 "lease": &framework.FieldSchema{ 54 Type: framework.TypeInt, 55 Description: `Deprecated: use "ttl" instead. TTL time in 56seconds. Defaults to system/backend default TTL.`, 57 }, 58 59 "ttl": &framework.FieldSchema{ 60 Type: framework.TypeDurationSecond, 61 Description: `TTL for tokens issued by this backend. 62Defaults to system/backend default TTL time.`, 63 }, 64 }, 65 66 Callbacks: map[logical.Operation]framework.OperationFunc{ 67 logical.DeleteOperation: b.pathCertDelete, 68 logical.ReadOperation: b.pathCertRead, 69 logical.UpdateOperation: b.pathCertWrite, 70 }, 71 72 HelpSynopsis: pathCertHelpSyn, 73 HelpDescription: pathCertHelpDesc, 74 } 75} 76 77func (b *backend) Cert(s logical.Storage, n string) (*CertEntry, error) { 78 entry, err := s.Get("cert/" + strings.ToLower(n)) 79 if err != nil { 80 return nil, err 81 } 82 if entry == nil { 83 return nil, nil 84 } 85 86 var result CertEntry 87 if err := entry.DecodeJSON(&result); err != nil { 88 return nil, err 89 } 90 return &result, nil 91} 92 93func (b *backend) pathCertDelete( 94 req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 95 err := req.Storage.Delete("cert/" + strings.ToLower(d.Get("name").(string))) 96 if err != nil { 97 return nil, err 98 } 99 return nil, nil 100} 101 102func (b *backend) pathCertList( 103 req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 104 certs, err := req.Storage.List("cert/") 105 if err != nil { 106 return nil, err 107 } 108 return logical.ListResponse(certs), nil 109} 110 111func (b *backend) pathCertRead( 112 req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 113 cert, err := b.Cert(req.Storage, strings.ToLower(d.Get("name").(string))) 114 if err != nil { 115 return nil, err 116 } 117 if cert == nil { 118 return nil, nil 119 } 120 121 duration := cert.TTL 122 if duration == 0 { 123 duration = b.System().DefaultLeaseTTL() 124 } 125 126 return &logical.Response{ 127 Data: map[string]interface{}{ 128 "certificate": cert.Certificate, 129 "display_name": cert.DisplayName, 130 "policies": strings.Join(cert.Policies, ","), 131 "ttl": duration / time.Second, 132 }, 133 }, nil 134} 135 136func (b *backend) pathCertWrite( 137 req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 138 name := strings.ToLower(d.Get("name").(string)) 139 certificate := d.Get("certificate").(string) 140 displayName := d.Get("display_name").(string) 141 policies := policyutil.ParsePolicies(d.Get("policies").(string)) 142 143 // Default the display name to the certificate name if not given 144 if displayName == "" { 145 displayName = name 146 } 147 148 parsed := parsePEM([]byte(certificate)) 149 if len(parsed) == 0 { 150 return logical.ErrorResponse("failed to parse certificate"), nil 151 } 152 153 // If the certificate is not a CA cert, then ensure that x509.ExtKeyUsageClientAuth is set 154 if !parsed[0].IsCA && parsed[0].ExtKeyUsage != nil { 155 var clientAuth bool 156 for _, usage := range parsed[0].ExtKeyUsage { 157 if usage == x509.ExtKeyUsageClientAuth || usage == x509.ExtKeyUsageAny { 158 clientAuth = true 159 break 160 } 161 } 162 if !clientAuth { 163 return logical.ErrorResponse("non-CA certificates should have TLS client authentication set as an extended key usage"), nil 164 } 165 } 166 167 certEntry := &CertEntry{ 168 Name: name, 169 Certificate: certificate, 170 DisplayName: displayName, 171 Policies: policies, 172 } 173 174 // Parse the lease duration or default to backend/system default 175 maxTTL := b.System().MaxLeaseTTL() 176 ttl := time.Duration(d.Get("ttl").(int)) * time.Second 177 if ttl == time.Duration(0) { 178 ttl = time.Second * time.Duration(d.Get("lease").(int)) 179 } 180 if ttl > maxTTL { 181 return logical.ErrorResponse(fmt.Sprintf("Given TTL of %d seconds greater than current mount/system default of %d seconds", ttl/time.Second, maxTTL/time.Second)), nil 182 } 183 if ttl > time.Duration(0) { 184 certEntry.TTL = ttl 185 } 186 187 // Store it 188 entry, err := logical.StorageEntryJSON("cert/"+name, certEntry) 189 if err != nil { 190 return nil, err 191 } 192 if err := req.Storage.Put(entry); err != nil { 193 return nil, err 194 } 195 return nil, nil 196} 197 198type CertEntry struct { 199 Name string 200 Certificate string 201 DisplayName string 202 Policies []string 203 TTL time.Duration 204} 205 206const pathCertHelpSyn = ` 207Manage trusted certificates used for authentication. 208` 209 210const pathCertHelpDesc = ` 211This endpoint allows you to create, read, update, and delete trusted certificates 212that are allowed to authenticate. 213 214Deleting a certificate will not revoke auth for prior authenticated connections. 215To do this, do a revoke on "login". If you don't need to revoke login immediately, 216then the next renew will cause the lease to expire. 217` 218