1package cert 2 3import ( 4 "context" 5 "crypto/x509" 6 "fmt" 7 "strings" 8 "time" 9 10 sockaddr "github.com/hashicorp/go-sockaddr" 11 "github.com/hashicorp/vault/sdk/framework" 12 "github.com/hashicorp/vault/sdk/helper/tokenutil" 13 "github.com/hashicorp/vault/sdk/logical" 14) 15 16func pathListCerts(b *backend) *framework.Path { 17 return &framework.Path{ 18 Pattern: "certs/?", 19 20 Callbacks: map[logical.Operation]framework.OperationFunc{ 21 logical.ListOperation: b.pathCertList, 22 }, 23 24 HelpSynopsis: pathCertHelpSyn, 25 HelpDescription: pathCertHelpDesc, 26 } 27} 28 29func pathCerts(b *backend) *framework.Path { 30 p := &framework.Path{ 31 Pattern: "certs/" + framework.GenericNameRegex("name"), 32 Fields: map[string]*framework.FieldSchema{ 33 "name": &framework.FieldSchema{ 34 Type: framework.TypeString, 35 Description: "The name of the certificate", 36 }, 37 38 "certificate": &framework.FieldSchema{ 39 Type: framework.TypeString, 40 Description: `The public certificate that should be trusted. 41Must be x509 PEM encoded.`, 42 }, 43 44 "allowed_names": &framework.FieldSchema{ 45 Type: framework.TypeCommaStringSlice, 46 Description: `A comma-separated list of names. 47At least one must exist in either the Common Name or SANs. Supports globbing. 48This parameter is deprecated, please use allowed_common_names, allowed_dns_sans, 49allowed_email_sans, allowed_uri_sans.`, 50 }, 51 52 "allowed_common_names": &framework.FieldSchema{ 53 Type: framework.TypeCommaStringSlice, 54 Description: `A comma-separated list of names. 55At least one must exist in the Common Name. Supports globbing.`, 56 }, 57 58 "allowed_dns_sans": &framework.FieldSchema{ 59 Type: framework.TypeCommaStringSlice, 60 Description: `A comma-separated list of DNS names. 61At least one must exist in the SANs. Supports globbing.`, 62 }, 63 64 "allowed_email_sans": &framework.FieldSchema{ 65 Type: framework.TypeCommaStringSlice, 66 Description: `A comma-separated list of Email Addresses. 67At least one must exist in the SANs. Supports globbing.`, 68 }, 69 70 "allowed_uri_sans": &framework.FieldSchema{ 71 Type: framework.TypeCommaStringSlice, 72 Description: `A comma-separated list of URIs. 73At least one must exist in the SANs. Supports globbing.`, 74 }, 75 76 "allowed_organizational_units": &framework.FieldSchema{ 77 Type: framework.TypeCommaStringSlice, 78 Description: `A comma-separated list of Organizational Units names. 79At least one must exist in the OU field.`, 80 }, 81 82 "required_extensions": &framework.FieldSchema{ 83 Type: framework.TypeCommaStringSlice, 84 Description: `A comma-separated string or array of extensions 85formatted as "oid:value". Expects the extension value to be some type of ASN1 encoded string. 86All values much match. Supports globbing on "value".`, 87 }, 88 89 "display_name": &framework.FieldSchema{ 90 Type: framework.TypeString, 91 Description: `The display name to use for clients using this 92certificate.`, 93 }, 94 95 "policies": &framework.FieldSchema{ 96 Type: framework.TypeCommaStringSlice, 97 Description: tokenutil.DeprecationText("token_policies"), 98 Deprecated: true, 99 }, 100 101 "lease": &framework.FieldSchema{ 102 Type: framework.TypeInt, 103 Description: tokenutil.DeprecationText("token_ttl"), 104 Deprecated: true, 105 }, 106 107 "ttl": &framework.FieldSchema{ 108 Type: framework.TypeDurationSecond, 109 Description: tokenutil.DeprecationText("token_ttl"), 110 Deprecated: true, 111 }, 112 113 "max_ttl": &framework.FieldSchema{ 114 Type: framework.TypeDurationSecond, 115 Description: tokenutil.DeprecationText("token_max_ttl"), 116 Deprecated: true, 117 }, 118 119 "period": &framework.FieldSchema{ 120 Type: framework.TypeDurationSecond, 121 Description: tokenutil.DeprecationText("token_period"), 122 Deprecated: true, 123 }, 124 125 "bound_cidrs": &framework.FieldSchema{ 126 Type: framework.TypeCommaStringSlice, 127 Description: tokenutil.DeprecationText("token_bound_cidrs"), 128 Deprecated: true, 129 }, 130 }, 131 132 Callbacks: map[logical.Operation]framework.OperationFunc{ 133 logical.DeleteOperation: b.pathCertDelete, 134 logical.ReadOperation: b.pathCertRead, 135 logical.UpdateOperation: b.pathCertWrite, 136 }, 137 138 HelpSynopsis: pathCertHelpSyn, 139 HelpDescription: pathCertHelpDesc, 140 } 141 142 tokenutil.AddTokenFields(p.Fields) 143 return p 144} 145 146func (b *backend) Cert(ctx context.Context, s logical.Storage, n string) (*CertEntry, error) { 147 entry, err := s.Get(ctx, "cert/"+strings.ToLower(n)) 148 if err != nil { 149 return nil, err 150 } 151 if entry == nil { 152 return nil, nil 153 } 154 155 var result CertEntry 156 if err := entry.DecodeJSON(&result); err != nil { 157 return nil, err 158 } 159 160 if result.TokenTTL == 0 && result.TTL > 0 { 161 result.TokenTTL = result.TTL 162 } 163 if result.TokenMaxTTL == 0 && result.MaxTTL > 0 { 164 result.TokenMaxTTL = result.MaxTTL 165 } 166 if result.TokenPeriod == 0 && result.Period > 0 { 167 result.TokenPeriod = result.Period 168 } 169 if len(result.TokenPolicies) == 0 && len(result.Policies) > 0 { 170 result.TokenPolicies = result.Policies 171 } 172 if len(result.TokenBoundCIDRs) == 0 && len(result.BoundCIDRs) > 0 { 173 result.TokenBoundCIDRs = result.BoundCIDRs 174 } 175 176 return &result, nil 177} 178 179func (b *backend) pathCertDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 180 err := req.Storage.Delete(ctx, "cert/"+strings.ToLower(d.Get("name").(string))) 181 if err != nil { 182 return nil, err 183 } 184 return nil, nil 185} 186 187func (b *backend) pathCertList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 188 certs, err := req.Storage.List(ctx, "cert/") 189 if err != nil { 190 return nil, err 191 } 192 return logical.ListResponse(certs), nil 193} 194 195func (b *backend) pathCertRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 196 cert, err := b.Cert(ctx, req.Storage, strings.ToLower(d.Get("name").(string))) 197 if err != nil { 198 return nil, err 199 } 200 if cert == nil { 201 return nil, nil 202 } 203 204 data := map[string]interface{}{ 205 "certificate": cert.Certificate, 206 "display_name": cert.DisplayName, 207 "allowed_names": cert.AllowedNames, 208 "allowed_common_names": cert.AllowedCommonNames, 209 "allowed_dns_sans": cert.AllowedDNSSANs, 210 "allowed_email_sans": cert.AllowedEmailSANs, 211 "allowed_uri_sans": cert.AllowedURISANs, 212 "allowed_organizational_units": cert.AllowedOrganizationalUnits, 213 "required_extensions": cert.RequiredExtensions, 214 } 215 cert.PopulateTokenData(data) 216 217 if cert.TTL > 0 { 218 data["ttl"] = int64(cert.TTL.Seconds()) 219 } 220 if cert.MaxTTL > 0 { 221 data["max_ttl"] = int64(cert.MaxTTL.Seconds()) 222 } 223 if cert.Period > 0 { 224 data["period"] = int64(cert.Period.Seconds()) 225 } 226 if len(cert.Policies) > 0 { 227 data["policies"] = data["token_policies"] 228 } 229 if len(cert.BoundCIDRs) > 0 { 230 data["bound_cidrs"] = data["token_bound_cidrs"] 231 } 232 233 return &logical.Response{ 234 Data: data, 235 }, nil 236} 237 238func (b *backend) pathCertWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 239 name := strings.ToLower(d.Get("name").(string)) 240 241 cert, err := b.Cert(ctx, req.Storage, name) 242 if err != nil { 243 return nil, err 244 } 245 246 if cert == nil { 247 cert = &CertEntry{ 248 Name: name, 249 } 250 } 251 252 // Get non tokenutil fields 253 if certificateRaw, ok := d.GetOk("certificate"); ok { 254 cert.Certificate = certificateRaw.(string) 255 } 256 if displayNameRaw, ok := d.GetOk("display_name"); ok { 257 cert.DisplayName = displayNameRaw.(string) 258 } 259 if allowedNamesRaw, ok := d.GetOk("allowed_names"); ok { 260 cert.AllowedNames = allowedNamesRaw.([]string) 261 } 262 if allowedCommonNamesRaw, ok := d.GetOk("allowed_common_names"); ok { 263 cert.AllowedCommonNames = allowedCommonNamesRaw.([]string) 264 } 265 if allowedDNSSANsRaw, ok := d.GetOk("allowed_dns_sans"); ok { 266 cert.AllowedDNSSANs = allowedDNSSANsRaw.([]string) 267 } 268 if allowedEmailSANsRaw, ok := d.GetOk("allowed_email_sans"); ok { 269 cert.AllowedEmailSANs = allowedEmailSANsRaw.([]string) 270 } 271 if allowedURISANsRaw, ok := d.GetOk("allowed_uri_sans"); ok { 272 cert.AllowedURISANs = allowedURISANsRaw.([]string) 273 } 274 if allowedOrganizationalUnitsRaw, ok := d.GetOk("allowed_organizational_units"); ok { 275 cert.AllowedOrganizationalUnits = allowedOrganizationalUnitsRaw.([]string) 276 } 277 if requiredExtensionsRaw, ok := d.GetOk("required_extensions"); ok { 278 cert.RequiredExtensions = requiredExtensionsRaw.([]string) 279 } 280 281 // Get tokenutil fields 282 if err := cert.ParseTokenFields(req, d); err != nil { 283 return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest 284 } 285 286 // Handle upgrade cases 287 { 288 if err := tokenutil.UpgradeValue(d, "policies", "token_policies", &cert.Policies, &cert.TokenPolicies); err != nil { 289 return logical.ErrorResponse(err.Error()), nil 290 } 291 292 if err := tokenutil.UpgradeValue(d, "ttl", "token_ttl", &cert.TTL, &cert.TokenTTL); err != nil { 293 return logical.ErrorResponse(err.Error()), nil 294 } 295 // Special case here for old lease value 296 _, ok := d.GetOk("token_ttl") 297 if !ok { 298 _, ok = d.GetOk("ttl") 299 if !ok { 300 ttlRaw, ok := d.GetOk("lease") 301 if ok { 302 cert.TTL = time.Duration(ttlRaw.(int)) * time.Second 303 cert.TokenTTL = cert.TTL 304 } 305 } 306 } 307 308 if err := tokenutil.UpgradeValue(d, "max_ttl", "token_max_ttl", &cert.MaxTTL, &cert.TokenMaxTTL); err != nil { 309 return logical.ErrorResponse(err.Error()), nil 310 } 311 312 if err := tokenutil.UpgradeValue(d, "period", "token_period", &cert.Period, &cert.TokenPeriod); err != nil { 313 return logical.ErrorResponse(err.Error()), nil 314 } 315 316 if err := tokenutil.UpgradeValue(d, "bound_cidrs", "token_bound_cidrs", &cert.BoundCIDRs, &cert.TokenBoundCIDRs); err != nil { 317 return logical.ErrorResponse(err.Error()), nil 318 } 319 } 320 321 var resp logical.Response 322 323 systemDefaultTTL := b.System().DefaultLeaseTTL() 324 if cert.TokenTTL > systemDefaultTTL { 325 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)) 326 } 327 systemMaxTTL := b.System().MaxLeaseTTL() 328 if cert.TokenMaxTTL > systemMaxTTL { 329 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)) 330 } 331 if cert.TokenMaxTTL != 0 && cert.TokenTTL > cert.TokenMaxTTL { 332 return logical.ErrorResponse("ttl should be shorter than max_ttl"), nil 333 } 334 if cert.TokenPeriod > systemMaxTTL { 335 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)) 336 } 337 338 // Default the display name to the certificate name if not given 339 if cert.DisplayName == "" { 340 cert.DisplayName = name 341 } 342 343 parsed := parsePEM([]byte(cert.Certificate)) 344 if len(parsed) == 0 { 345 return logical.ErrorResponse("failed to parse certificate"), nil 346 } 347 348 // If the certificate is not a CA cert, then ensure that x509.ExtKeyUsageClientAuth is set 349 if !parsed[0].IsCA && parsed[0].ExtKeyUsage != nil { 350 var clientAuth bool 351 for _, usage := range parsed[0].ExtKeyUsage { 352 if usage == x509.ExtKeyUsageClientAuth || usage == x509.ExtKeyUsageAny { 353 clientAuth = true 354 break 355 } 356 } 357 if !clientAuth { 358 return logical.ErrorResponse("non-CA certificates should have TLS client authentication set as an extended key usage"), nil 359 } 360 } 361 362 // Store it 363 entry, err := logical.StorageEntryJSON("cert/"+name, cert) 364 if err != nil { 365 return nil, err 366 } 367 if err := req.Storage.Put(ctx, entry); err != nil { 368 return nil, err 369 } 370 371 if len(resp.Warnings) == 0 { 372 return nil, nil 373 } 374 375 return &resp, nil 376} 377 378type CertEntry struct { 379 tokenutil.TokenParams 380 381 Name string 382 Certificate string 383 DisplayName string 384 Policies []string 385 TTL time.Duration 386 MaxTTL time.Duration 387 Period time.Duration 388 AllowedNames []string 389 AllowedCommonNames []string 390 AllowedDNSSANs []string 391 AllowedEmailSANs []string 392 AllowedURISANs []string 393 AllowedOrganizationalUnits []string 394 RequiredExtensions []string 395 BoundCIDRs []*sockaddr.SockAddrMarshaler 396} 397 398const pathCertHelpSyn = ` 399Manage trusted certificates used for authentication. 400` 401 402const pathCertHelpDesc = ` 403This endpoint allows you to create, read, update, and delete trusted certificates 404that are allowed to authenticate. 405 406Deleting a certificate will not revoke auth for prior authenticated connections. 407To do this, do a revoke on "login". If you don't need to revoke login immediately, 408then the next renew will cause the lease to expire. 409` 410