// pathConfigCertificateCreateUpdate is used to register an AWS Public Key that is
// used to verify the PKCS#7 signature of the instance identity document.
func (b *backend) pathConfigCertificateCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	certName := data.Get("cert_name").(string)
	if certName == "" {
		return logical.ErrorResponse("missing cert_name"), nil
	}

	b.configMutex.Lock()
	defer b.configMutex.Unlock()

	// Check if there is already a certificate entry registered.
	certEntry, err := b.nonLockedAWSPublicCertificateEntry(req.Storage, certName)
	if err != nil {
		return nil, err
	}
	if certEntry == nil {
		certEntry = &awsPublicCert{}
	}

	// Check if the value is provided by the client.
	certStrData, ok := data.GetOk("aws_public_cert")
	if ok {
		if certBytes, err := base64.StdEncoding.DecodeString(certStrData.(string)); err == nil {
			certEntry.AWSPublicCert = string(certBytes)
		} else {
			certEntry.AWSPublicCert = certStrData.(string)
		}
	} else {
		// aws_public_cert should be supplied for both create and update operations.
		// If it is not provided, throw an error.
		return logical.ErrorResponse("missing aws_public_cert"), nil
	}

	// If explicitly set to empty string, error out.
	if certEntry.AWSPublicCert == "" {
		return logical.ErrorResponse("invalid aws_public_cert"), nil
	}

	// Verify the certificate by decoding it and parsing it.
	publicCert, err := decodePEMAndParseCertificate(certEntry.AWSPublicCert)
	if err != nil {
		return nil, err
	}
	if publicCert == nil {
		return logical.ErrorResponse("invalid certificate; failed to decode and parse certificate"), nil
	}

	// Ensure that we have not
	// If none of the checks fail, save the provided certificate.
	entry, err := logical.StorageEntryJSON("config/certificate/"+certName, certEntry)
	if err != nil {
		return nil, err
	}

	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}

	return nil, nil
}
func (b *backend) pathConfigTidyRoletagBlacklistCreateUpdate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	b.configMutex.Lock()
	defer b.configMutex.Unlock()

	configEntry, err := b.nonLockedConfigTidyRoleTags(req.Storage)
	if err != nil {
		return nil, err
	}
	if configEntry == nil {
		configEntry = &tidyBlacklistRoleTagConfig{}
	}
	safetyBufferInt, ok := data.GetOk("safety_buffer")
	if ok {
		configEntry.SafetyBuffer = safetyBufferInt.(int)
	} else if req.Operation == logical.CreateOperation {
		configEntry.SafetyBuffer = data.Get("safety_buffer").(int)
	}
	disablePeriodicTidyBool, ok := data.GetOk("disable_periodic_tidy")
	if ok {
		configEntry.DisablePeriodicTidy = disablePeriodicTidyBool.(bool)
	} else if req.Operation == logical.CreateOperation {
		configEntry.DisablePeriodicTidy = data.Get("disable_periodic_tidy").(bool)
	}

	entry, err := logical.StorageEntryJSON(roletagBlacklistConfigPath, configEntry)
	if err != nil {
		return nil, err
	}

	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}

	return nil, nil
}
Пример #3
0
// handleMountTune is used to set config settings on a backend
func (b *SystemBackend) handleMountTune(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	path := data.Get("path").(string)
	if path == "" {
		return logical.ErrorResponse(
				"path must be specified as a string"),
			logical.ErrInvalidRequest
	}

	if !strings.HasSuffix(path, "/") {
		path += "/"
	}

	// Prevent protected paths from being changed
	for _, p := range untunableMounts {
		if strings.HasPrefix(path, p) {
			err := fmt.Errorf("[ERR] core: cannot tune '%s'", path)
			b.Backend.Logger().Print(err)
			return handleError(err)
		}
	}

	mountEntry := b.Core.router.MatchingMountEntry(path)
	if mountEntry == nil {
		err := fmt.Errorf("[ERR] sys: tune of path '%s' failed: no mount entry found", path)
		b.Backend.Logger().Print(err)
		return handleError(err)
	}

	newMountConfig := mountEntry.Config

	// Timing configuration parameters
	{
		var needTTLTune bool
		defTTLInt, ok := data.GetOk("default_lease_ttl")
		if ok {
			newMountConfig.DefaultLeaseTTL = time.Duration(defTTLInt.(int))
			needTTLTune = true
		}
		maxTTLInt, ok := data.GetOk("max_lease_ttl")
		if ok {
			newMountConfig.MaxLeaseTTL = time.Duration(maxTTLInt.(int))
			needTTLTune = true
		}
		if needTTLTune {
			if err := b.tuneMountTTLs(path, &mountEntry.Config, &newMountConfig); err != nil {
				b.Backend.Logger().Printf("[ERR] sys: tune of path '%s' failed: %v", path, err)
				return handleError(err)
			}
		}
	}

	return nil, nil
}
Пример #4
0
func (b *backend) pathConfigWrite(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	organization := data.Get("organization").(string)
	baseURL := data.Get("base_url").(string)
	if len(baseURL) != 0 {
		_, err := url.Parse(baseURL)
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf("Error parsing given base_url: %s", err)), nil
		}
	}

	var ttl time.Duration
	var err error
	ttlRaw, ok := data.GetOk("ttl")
	if !ok || len(ttlRaw.(string)) == 0 {
		ttl = 0
	} else {
		ttl, err = time.ParseDuration(ttlRaw.(string))
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf("Invalid 'ttl':%s", err)), nil
		}
	}

	var maxTTL time.Duration
	maxTTLRaw, ok := data.GetOk("max_ttl")
	if !ok || len(maxTTLRaw.(string)) == 0 {
		maxTTL = 0
	} else {
		maxTTL, err = time.ParseDuration(maxTTLRaw.(string))
		if err != nil {
			return logical.ErrorResponse(fmt.Sprintf("Invalid 'max_ttl':%s", err)), nil
		}
	}

	entry, err := logical.StorageEntryJSON("config", config{
		Org:     organization,
		BaseURL: baseURL,
		TTL:     ttl,
		MaxTTL:  maxTTL,
	})

	if err != nil {
		return nil, err
	}

	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}

	return nil, nil
}
Пример #5
0
func pathConfigWrite(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	name := d.Get("name").(string)

	// Check if the policy already exists
	policy, err := getPolicy(req, name)
	if err != nil {
		return nil, err
	}
	if policy == nil {
		return logical.ErrorResponse(
				fmt.Sprintf("no existing role named %s could be found", name)),
			logical.ErrInvalidRequest
	}

	persistNeeded := false

	minDecryptionVersion := d.Get("min_decryption_version").(int)
	if minDecryptionVersion != 0 &&
		minDecryptionVersion != policy.MinDecryptionVersion {
		policy.MinDecryptionVersion = minDecryptionVersion
		persistNeeded = true
	}

	allowDeletionInt, ok := d.GetOk("deletion_allowed")
	if ok {
		allowDeletion := allowDeletionInt.(bool)
		if allowDeletion != policy.DeletionAllowed {
			policy.DeletionAllowed = allowDeletion
			persistNeeded = true
		}
	}

	if !persistNeeded {
		return nil, nil
	}

	return nil, policy.Persist(req.Storage, name)
}
Пример #6
0
func (b *backend) userCreateUpdate(req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	username := strings.ToLower(d.Get("username").(string))
	userEntry, err := b.user(req.Storage, username)
	if err != nil {
		return nil, err
	}
	// Due to existence check, user will only be nil if it's a create operation
	if userEntry == nil {
		userEntry = &UserEntry{}
	}

	if _, ok := d.GetOk("password"); ok {
		userErr, intErr := b.updateUserPassword(req, d, userEntry)
		if intErr != nil {
			return nil, err
		}
		if userErr != nil {
			return logical.ErrorResponse(userErr.Error()), logical.ErrInvalidRequest
		}
	}

	if policiesRaw, ok := d.GetOk("policies"); ok {
		userEntry.Policies = policyutil.ParsePolicies(policiesRaw.(string))
	}

	ttlStr := userEntry.TTL.String()
	if ttlStrRaw, ok := d.GetOk("ttl"); ok {
		ttlStr = ttlStrRaw.(string)
	}

	maxTTLStr := userEntry.MaxTTL.String()
	if maxTTLStrRaw, ok := d.GetOk("max_ttl"); ok {
		maxTTLStr = maxTTLStrRaw.(string)
	}

	userEntry.TTL, userEntry.MaxTTL, err = b.SanitizeTTLStr(ttlStr, maxTTLStr)
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("err: %s", err)), nil
	}

	return nil, b.setUser(req.Storage, username, userEntry)
}
Пример #7
0
func (b *backend) pathWriteURL(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	entries, err := getURLs(req)
	if err != nil {
		return nil, err
	}
	if entries == nil {
		entries = &urlEntries{
			IssuingCertificates:   []string{},
			CRLDistributionPoints: []string{},
			OCSPServers:           []string{},
		}
	}

	if urlsInt, ok := data.GetOk("issuing_certificates"); ok {
		splitURLs := strings.Split(urlsInt.(string), ",")
		entries.IssuingCertificates = splitURLs
		if badUrl := validateURLs(entries.IssuingCertificates); badUrl != "" {
			return logical.ErrorResponse(fmt.Sprintf(
				"invalid URL found in issuing certificates: %s", badUrl)), nil
		}
	}
	if urlsInt, ok := data.GetOk("crl_distribution_points"); ok {
		splitURLs := strings.Split(urlsInt.(string), ",")
		entries.CRLDistributionPoints = splitURLs
		if badUrl := validateURLs(entries.CRLDistributionPoints); badUrl != "" {
			return logical.ErrorResponse(fmt.Sprintf(
				"invalid URL found in CRL distribution points: %s", badUrl)), nil
		}
	}
	if urlsInt, ok := data.GetOk("ocsp_servers"); ok {
		splitURLs := strings.Split(urlsInt.(string), ",")
		entries.OCSPServers = splitURLs
		if badUrl := validateURLs(entries.OCSPServers); badUrl != "" {
			return logical.ErrorResponse(fmt.Sprintf(
				"invalid URL found in OCSP servers: %s", badUrl)), nil
		}
	}

	return nil, writeURLs(req, entries)
}
Пример #8
0
func (b *backend) pathEncryptWrite(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	name := d.Get("name").(string)

	valueRaw, ok := d.GetOk("plaintext")
	if !ok {
		return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest
	}
	value := valueRaw.(string)

	// Decode the context if any
	contextRaw := d.Get("context").(string)
	var context []byte
	var err error
	if len(contextRaw) != 0 {
		context, err = base64.StdEncoding.DecodeString(contextRaw)
		if err != nil {
			return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
		}
	}

	// Decode the nonce if any
	nonceRaw := d.Get("nonce").(string)
	var nonce []byte
	if len(nonceRaw) != 0 {
		nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
		if err != nil {
			return logical.ErrorResponse("failed to base64-decode nonce"), logical.ErrInvalidRequest
		}
	}

	// Get the policy
	var p *policy
	var lock *sync.RWMutex
	var upserted bool
	if req.Operation == logical.CreateOperation {
		convergent := d.Get("convergent_encryption").(bool)
		if convergent && len(context) == 0 {
			return logical.ErrorResponse("convergent encryption requires derivation to be enabled, so context is required"), nil
		}

		polReq := policyRequest{
			storage:    req.Storage,
			name:       name,
			derived:    len(context) != 0,
			convergent: convergent,
		}

		keyType := d.Get("type").(string)
		switch keyType {
		case "aes256-gcm96":
			polReq.keyType = keyType_AES256_GCM96
		case "ecdsa-p256":
			return logical.ErrorResponse(fmt.Sprintf("key type %v not supported for this operation", keyType)), logical.ErrInvalidRequest
		default:
			return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest
		}

		p, lock, upserted, err = b.lm.GetPolicyUpsert(polReq)

	} else {
		p, lock, err = b.lm.GetPolicyShared(req.Storage, name)
	}
	if lock != nil {
		defer lock.RUnlock()
	}
	if err != nil {
		return nil, err
	}
	if p == nil {
		return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
	}

	ciphertext, err := p.Encrypt(context, nonce, value)
	if err != nil {
		switch err.(type) {
		case errutil.UserError:
			return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
		case errutil.InternalError:
			return nil, err
		default:
			return nil, err
		}
	}

	if ciphertext == "" {
		return nil, fmt.Errorf("empty ciphertext returned")
	}

	// Generate the response
	resp := &logical.Response{
		Data: map[string]interface{}{
			"ciphertext": ciphertext,
		},
	}

	if req.Operation == logical.CreateOperation && !upserted {
		resp.AddWarning("Attempted creation of the key during the encrypt operation, but it was created beforehand")
	}

	return resp, nil
}
Пример #9
0
// pathConfigClientCreateUpdate is used to register the 'aws_secret_key' and 'aws_access_key'
// that can be used to interact with AWS EC2 API.
func (b *backend) pathConfigClientCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	b.configMutex.Lock()
	defer b.configMutex.Unlock()

	configEntry, err := b.nonLockedClientConfigEntry(req.Storage)
	if err != nil {
		return nil, err
	}
	if configEntry == nil {
		configEntry = &clientConfig{}
	}

	changedCreds := false

	accessKeyStr, ok := data.GetOk("access_key")
	if ok {
		if configEntry.AccessKey != accessKeyStr.(string) {
			changedCreds = true
			configEntry.AccessKey = accessKeyStr.(string)
		}
	} else if req.Operation == logical.CreateOperation {
		// Use the default
		configEntry.AccessKey = data.Get("access_key").(string)
	}

	secretKeyStr, ok := data.GetOk("secret_key")
	if ok {
		if configEntry.SecretKey != secretKeyStr.(string) {
			changedCreds = true
			configEntry.SecretKey = secretKeyStr.(string)
		}
	} else if req.Operation == logical.CreateOperation {
		configEntry.SecretKey = data.Get("secret_key").(string)
	}

	endpointStr, ok := data.GetOk("endpoint")
	if ok {
		if configEntry.Endpoint != endpointStr.(string) {
			changedCreds = true
			configEntry.Endpoint = endpointStr.(string)
		}
	} else if req.Operation == logical.CreateOperation {
		configEntry.Endpoint = data.Get("endpoint").(string)
	}

	// Since this endpoint supports both create operation and update operation,
	// the error checks for access_key and secret_key not being set are not present.
	// This allows calling this endpoint multiple times to provide the values.
	// Hence, the readers of this endpoint should do the validation on
	// the validation of keys before using them.
	entry, err := logical.StorageEntryJSON("config/client", configEntry)
	if err != nil {
		return nil, err
	}

	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}

	if changedCreds {
		b.flushCachedEC2Clients()
		b.flushCachedIAMClients()
	}

	return nil, nil
}
Пример #10
0
func (ts *TokenStore) tokenStoreRoleCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("role_name").(string)
	if name == "" {
		return logical.ErrorResponse("role name cannot be empty"), nil
	}
	entry, err := ts.tokenStoreRole(name)
	if err != nil {
		return nil, err
	}

	// Due to the existence check, entry will only be nil if it's a create
	// operation, so just create a new one
	if entry == nil {
		entry = &tsRoleEntry{
			Name: name,
		}
	}

	// In this series of blocks, if we do not find a user-provided value and
	// it's a creation operation, we call data.Get to get the appropriate
	// default

	orphanInt, ok := data.GetOk("orphan")
	if ok {
		entry.Orphan = orphanInt.(bool)
	} else if req.Operation == logical.CreateOperation {
		entry.Orphan = data.Get("orphan").(bool)
	}

	periodInt, ok := data.GetOk("period")
	if ok {
		entry.Period = time.Second * time.Duration(periodInt.(int))
	} else if req.Operation == logical.CreateOperation {
		entry.Period = time.Second * time.Duration(data.Get("period").(int))
	}

	pathSuffixInt, ok := data.GetOk("path_suffix")
	if ok {
		pathSuffix := pathSuffixInt.(string)
		if pathSuffix != "" {
			matched := pathSuffixSanitize.MatchString(pathSuffix)
			if !matched {
				return logical.ErrorResponse(fmt.Sprintf("given role path suffix contains invalid characters; must match %s", pathSuffixSanitize.String())), nil
			}
			entry.PathSuffix = pathSuffix
		}
	} else if req.Operation == logical.CreateOperation {
		entry.PathSuffix = data.Get("path_suffix").(string)
	}

	allowedPoliciesInt, ok := data.GetOk("allowed_policies")
	if ok {
		allowedPolicies := allowedPoliciesInt.(string)
		if allowedPolicies != "" {
			entry.AllowedPolicies = strings.Split(allowedPolicies, ",")
		}
	} else if req.Operation == logical.CreateOperation {
		entry.AllowedPolicies = strings.Split(data.Get("allowed_policies").(string), ",")
	}

	// Store it
	jsonEntry, err := logical.StorageEntryJSON(fmt.Sprintf("%s%s", rolesPrefix, name), entry)
	if err != nil {
		return nil, err
	}
	if err := ts.view.Put(jsonEntry); err != nil {
		return nil, err
	}

	return nil, nil
}
Пример #11
0
// pathRoleCreateUpdate is used to associate Vault policies to a given AMI ID.
func (b *backend) pathRoleCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {

	roleName := strings.ToLower(data.Get("role").(string))
	if roleName == "" {
		return logical.ErrorResponse("missing role"), nil
	}

	b.roleMutex.Lock()
	defer b.roleMutex.Unlock()

	roleEntry, err := b.nonLockedAWSRole(req.Storage, roleName)
	if err != nil {
		return nil, err
	}
	if roleEntry == nil {
		roleEntry = &awsRoleEntry{}
	}

	// Fetch and set the bound parameters. There can't be default values
	// for these.
	if boundAmiIDRaw, ok := data.GetOk("bound_ami_id"); ok {
		roleEntry.BoundAmiID = boundAmiIDRaw.(string)
	}

	if boundAccountIDRaw, ok := data.GetOk("bound_account_id"); ok {
		roleEntry.BoundAccountID = boundAccountIDRaw.(string)
	}

	if boundIamRoleARNRaw, ok := data.GetOk("bound_iam_role_arn"); ok {
		roleEntry.BoundIamRoleARN = boundIamRoleARNRaw.(string)
	}

	if boundIamInstanceProfileARNRaw, ok := data.GetOk("bound_iam_instance_profile_arn"); ok {
		roleEntry.BoundIamInstanceProfileARN = boundIamInstanceProfileARNRaw.(string)
	}

	// Ensure that at least one bound is set on the role
	switch {
	case roleEntry.BoundAccountID != "":
	case roleEntry.BoundAmiID != "":
	case roleEntry.BoundIamInstanceProfileARN != "":
	case roleEntry.BoundIamRoleARN != "":
	default:

		return logical.ErrorResponse("at least be one bound parameter should be specified on the role"), nil
	}

	policiesStr, ok := data.GetOk("policies")
	if ok {
		roleEntry.Policies = policyutil.ParsePolicies(policiesStr.(string))
	} else if req.Operation == logical.CreateOperation {
		roleEntry.Policies = []string{"default"}
	}

	disallowReauthenticationBool, ok := data.GetOk("disallow_reauthentication")
	if ok {
		roleEntry.DisallowReauthentication = disallowReauthenticationBool.(bool)
	} else if req.Operation == logical.CreateOperation {
		roleEntry.DisallowReauthentication = data.Get("disallow_reauthentication").(bool)
	}

	allowInstanceMigrationBool, ok := data.GetOk("allow_instance_migration")
	if ok {
		roleEntry.AllowInstanceMigration = allowInstanceMigrationBool.(bool)
	} else if req.Operation == logical.CreateOperation {
		roleEntry.AllowInstanceMigration = data.Get("allow_instance_migration").(bool)
	}

	var resp logical.Response

	ttlRaw, ok := data.GetOk("ttl")
	if ok {
		ttl := time.Duration(ttlRaw.(int)) * time.Second
		defaultLeaseTTL := b.System().DefaultLeaseTTL()
		if ttl > defaultLeaseTTL {
			resp.AddWarning(fmt.Sprintf("Given ttl of %d seconds greater than current mount/system default of %d seconds; ttl will be capped at login time", ttl/time.Second, defaultLeaseTTL/time.Second))
		}
		roleEntry.TTL = ttl
	} else if req.Operation == logical.CreateOperation {
		roleEntry.TTL = time.Duration(data.Get("ttl").(int)) * time.Second
	}

	maxTTLInt, ok := data.GetOk("max_ttl")
	if ok {
		maxTTL := time.Duration(maxTTLInt.(int)) * time.Second
		systemMaxTTL := b.System().MaxLeaseTTL()
		if maxTTL > systemMaxTTL {
			resp.AddWarning(fmt.Sprintf("Given max_ttl of %d seconds greater than current mount/system default of %d seconds; max_ttl will be capped at login time", maxTTL/time.Second, systemMaxTTL/time.Second))
		}

		if maxTTL < time.Duration(0) {
			return logical.ErrorResponse("max_ttl cannot be negative"), nil
		}

		roleEntry.MaxTTL = maxTTL
	} else if req.Operation == logical.CreateOperation {
		roleEntry.MaxTTL = time.Duration(data.Get("max_ttl").(int)) * time.Second
	}

	if roleEntry.MaxTTL != 0 && roleEntry.MaxTTL < roleEntry.TTL {
		return logical.ErrorResponse("ttl should be shorter than max_ttl"), nil
	}

	roleTagStr, ok := data.GetOk("role_tag")
	if ok {
		roleEntry.RoleTag = roleTagStr.(string)
		// There is a limit of 127 characters on the tag key for AWS EC2 instances.
		// Complying to that requirement, do not allow the value of 'key' to be more than that.
		if len(roleEntry.RoleTag) > 127 {
			return logical.ErrorResponse("length of role tag exceeds the EC2 key limit of 127 characters"), nil
		}
	} else if req.Operation == logical.CreateOperation {
		roleEntry.RoleTag = data.Get("role_tag").(string)
	}

	if roleEntry.HMACKey == "" {
		roleEntry.HMACKey, err = uuid.GenerateUUID()
		if err != nil {
			return nil, fmt.Errorf("failed to generate role HMAC key: %v", err)
		}
	}

	if err := b.nonLockedSetAWSRole(req.Storage, roleName, roleEntry); err != nil {
		return nil, err
	}

	if len(resp.Warnings()) == 0 {
		return nil, nil
	}

	return &resp, nil
}
Пример #12
0
// pathConfigCertificateCreateUpdate is used to register an AWS Public Key that
// is used to verify the PKCS#7 signature of the instance identity document.
func (b *backend) pathConfigCertificateCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	certName := data.Get("cert_name").(string)
	if certName == "" {
		return logical.ErrorResponse("missing certificate name"), nil
	}

	b.configMutex.Lock()
	defer b.configMutex.Unlock()

	// Check if there is already a certificate entry registered
	certEntry, err := b.nonLockedAWSPublicCertificateEntry(req.Storage, certName)
	if err != nil {
		return nil, err
	}
	if certEntry == nil {
		certEntry = &awsPublicCert{}
	}

	// Check if type information is provided
	certTypeRaw, ok := data.GetOk("type")
	if ok {
		certEntry.Type = strings.ToLower(certTypeRaw.(string))
	} else if req.Operation == logical.CreateOperation {
		certEntry.Type = data.Get("type").(string)
	}

	switch certEntry.Type {
	case "pkcs7":
	case "identity":
	default:
		return logical.ErrorResponse(fmt.Sprintf("invalid certificate type %q", certEntry.Type)), nil
	}

	// Check if the value is provided by the client
	certStrData, ok := data.GetOk("aws_public_cert")
	if ok {
		if certBytes, err := base64.StdEncoding.DecodeString(certStrData.(string)); err == nil {
			certEntry.AWSPublicCert = string(certBytes)
		} else {
			certEntry.AWSPublicCert = certStrData.(string)
		}
	} else {
		// aws_public_cert should be supplied for both create and update operations.
		// If it is not provided, throw an error.
		return logical.ErrorResponse("missing aws_public_cert"), nil
	}

	// If explicitly set to empty string, error out
	if certEntry.AWSPublicCert == "" {
		return logical.ErrorResponse("invalid aws_public_cert"), nil
	}

	// Verify the certificate by decoding it and parsing it
	publicCert, err := decodePEMAndParseCertificate(certEntry.AWSPublicCert)
	if err != nil {
		return nil, err
	}
	if publicCert == nil {
		return logical.ErrorResponse("invalid certificate; failed to decode and parse certificate"), nil
	}

	// If none of the checks fail, save the provided certificate
	if err := b.nonLockedSetAWSPublicCertificateEntry(req.Storage, certName, certEntry); err != nil {
		return nil, err
	}

	return nil, nil
}
Пример #13
0
// generateCreationBundle is a shared function that reads parameters supplied
// from the various endpoints and generates a creationBundle with the
// parameters that can be used to issue or sign
func generateCreationBundle(b *backend,
	role *roleEntry,
	signingBundle *caInfoBundle,
	csr *x509.CertificateRequest,
	req *logical.Request,
	data *framework.FieldData) (*creationBundle, error) {
	var err error
	var ok bool

	// Get the common name
	var cn string
	{
		if csr != nil {
			if role.UseCSRCommonName {
				cn = csr.Subject.CommonName
			}
		}
		if cn == "" {
			cn = data.Get("common_name").(string)
			if cn == "" {
				return nil, errutil.UserError{Err: `the common_name field is required, or must be provided in a CSR with "use_csr_common_name" set to true`}
			}
		}
	}

	// Read in alternate names -- DNS and email addresses
	dnsNames := []string{}
	emailAddresses := []string{}
	{
		if !data.Get("exclude_cn_from_sans").(bool) {
			if strings.Contains(cn, "@") {
				// Note: emails are not disallowed if the role's email protection
				// flag is false, because they may well be included for
				// informational purposes; it is up to the verifying party to
				// ensure that email addresses in a subject alternate name can be
				// used for the purpose for which they are presented
				emailAddresses = append(emailAddresses, cn)
			} else {
				dnsNames = append(dnsNames, cn)
			}
		}
		cnAltInt, ok := data.GetOk("alt_names")
		if ok {
			cnAlt := cnAltInt.(string)
			if len(cnAlt) != 0 {
				for _, v := range strings.Split(cnAlt, ",") {
					if strings.Contains(v, "@") {
						emailAddresses = append(emailAddresses, v)
					} else {
						dnsNames = append(dnsNames, v)
					}
				}
			}
		}

		// Check for bad email and/or DNS names
		badName, err := validateNames(req, dnsNames, role)
		if len(badName) != 0 {
			return nil, errutil.UserError{Err: fmt.Sprintf(
				"name %s not allowed by this role", badName)}
		} else if err != nil {
			return nil, errutil.InternalError{Err: fmt.Sprintf(
				"error validating name %s: %s", badName, err)}
		}

		badName, err = validateNames(req, emailAddresses, role)
		if len(badName) != 0 {
			return nil, errutil.UserError{Err: fmt.Sprintf(
				"email %s not allowed by this role", badName)}
		} else if err != nil {
			return nil, errutil.InternalError{Err: fmt.Sprintf(
				"error validating name %s: %s", badName, err)}
		}
	}

	// Get and verify any IP SANs
	ipAddresses := []net.IP{}
	var ipAltInt interface{}
	{
		ipAltInt, ok = data.GetOk("ip_sans")
		if ok {
			ipAlt := ipAltInt.(string)
			if len(ipAlt) != 0 {
				if !role.AllowIPSANs {
					return nil, errutil.UserError{Err: fmt.Sprintf(
						"IP Subject Alternative Names are not allowed in this role, but was provided %s", ipAlt)}
				}
				for _, v := range strings.Split(ipAlt, ",") {
					parsedIP := net.ParseIP(v)
					if parsedIP == nil {
						return nil, errutil.UserError{Err: fmt.Sprintf(
							"the value '%s' is not a valid IP address", v)}
					}
					ipAddresses = append(ipAddresses, parsedIP)
				}
			}
		}
	}

	// Get the TTL and very it against the max allowed
	var ttlField string
	var ttl time.Duration
	var maxTTL time.Duration
	var ttlFieldInt interface{}
	{
		ttlFieldInt, ok = data.GetOk("ttl")
		if !ok {
			ttlField = role.TTL
		} else {
			ttlField = ttlFieldInt.(string)
		}

		if len(ttlField) == 0 {
			ttl = b.System().DefaultLeaseTTL()
		} else {
			ttl, err = time.ParseDuration(ttlField)
			if err != nil {
				return nil, errutil.UserError{Err: fmt.Sprintf(
					"invalid requested ttl: %s", err)}
			}
		}

		if len(role.MaxTTL) == 0 {
			maxTTL = b.System().MaxLeaseTTL()
		} else {
			maxTTL, err = time.ParseDuration(role.MaxTTL)
			if err != nil {
				return nil, errutil.UserError{Err: fmt.Sprintf(
					"invalid ttl: %s", err)}
			}
		}

		if ttl > maxTTL {
			// Don't error if they were using system defaults, only error if
			// they specifically chose a bad TTL
			if len(ttlField) == 0 {
				ttl = maxTTL
			} else {
				return nil, errutil.UserError{Err: fmt.Sprintf(
					"ttl is larger than maximum allowed (%d)", maxTTL/time.Second)}
			}
		}

		// If it's not self-signed, verify that the issued certificate won't be
		// valid past the lifetime of the CA certificate
		if signingBundle != nil &&
			time.Now().Add(ttl).After(signingBundle.Certificate.NotAfter) {
			return nil, errutil.UserError{Err: fmt.Sprintf(
				"cannot satisfy request, as TTL is beyond the expiration of the CA certificate")}
		}
	}

	// Build up usages
	var extUsage certExtKeyUsage
	{
		if role.ServerFlag {
			extUsage = extUsage | serverExtKeyUsage
		}
		if role.ClientFlag {
			extUsage = extUsage | clientExtKeyUsage
		}
		if role.CodeSigningFlag {
			extUsage = extUsage | codeSigningExtKeyUsage
		}
		if role.EmailProtectionFlag {
			extUsage = extUsage | emailProtectionExtKeyUsage
		}
	}

	creationBundle := &creationBundle{
		CommonName:     cn,
		DNSNames:       dnsNames,
		EmailAddresses: emailAddresses,
		IPAddresses:    ipAddresses,
		KeyType:        role.KeyType,
		KeyBits:        role.KeyBits,
		SigningBundle:  signingBundle,
		TTL:            ttl,
		KeyUsage:       x509.KeyUsage(parseKeyUsages(role.KeyUsage)),
		ExtKeyUsage:    extUsage,
	}

	// Don't deal with URLs or max path length if it's self-signed, as these
	// normally come from the signing bundle
	if signingBundle == nil {
		return creationBundle, nil
	}

	// This will have been read in from the getURLs function
	creationBundle.URLs = signingBundle.URLs

	// If the max path length in the role is not nil, it was specified at
	// generation time with the max_path_length parameter; otherwise derive it
	// from the signing certificate
	if role.MaxPathLength != nil {
		creationBundle.MaxPathLength = *role.MaxPathLength
	} else {
		switch {
		case signingBundle.Certificate.MaxPathLen < 0:
			creationBundle.MaxPathLength = -1
		case signingBundle.Certificate.MaxPathLen == 0 &&
			signingBundle.Certificate.MaxPathLenZero:
			// The signing function will ensure that we do not issue a CA cert
			creationBundle.MaxPathLength = 0
		default:
			// If this takes it to zero, we handle this case later if
			// necessary
			creationBundle.MaxPathLength = signingBundle.Certificate.MaxPathLen - 1
		}
	}

	return creationBundle, nil
}
Пример #14
0
func (b *backend) pathCASignIntermediate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	var err error

	format := getFormat(data)
	if format == "" {
		return logical.ErrorResponse(
			`The "format" path parameter must be "pem" or "der"`,
		), nil
	}

	role := &roleEntry{
		TTL:              data.Get("ttl").(string),
		AllowLocalhost:   true,
		AllowAnyName:     true,
		AllowIPSANs:      true,
		EnforceHostnames: false,
		KeyType:          "any",
	}

	if cn := data.Get("common_name").(string); len(cn) == 0 {
		role.UseCSRCommonName = true
	}

	var caErr error
	signingBundle, caErr := fetchCAInfo(req)
	switch caErr.(type) {
	case certutil.UserError:
		return nil, certutil.UserError{Err: fmt.Sprintf(
			"could not fetch the CA certificate (was one set?): %s", caErr)}
	case certutil.InternalError:
		return nil, certutil.InternalError{Err: fmt.Sprintf(
			"error fetching CA certificate: %s", caErr)}
	}

	useCSRValues := data.Get("use_csr_values").(bool)

	maxPathLengthIface, ok := data.GetOk("max_path_length")
	if ok {
		maxPathLength := maxPathLengthIface.(int)
		role.MaxPathLength = &maxPathLength
	}

	parsedBundle, err := signCert(b, role, signingBundle, true, useCSRValues, req, data)
	if err != nil {
		switch err.(type) {
		case certutil.UserError:
			return logical.ErrorResponse(err.Error()), nil
		case certutil.InternalError:
			return nil, err
		}
	}

	cb, err := parsedBundle.ToCertBundle()
	if err != nil {
		return nil, fmt.Errorf("Error converting raw cert bundle to cert bundle: %s", err)
	}

	resp := &logical.Response{
		Data: map[string]interface{}{
			"expiration":    int64(parsedBundle.Certificate.NotAfter.Unix()),
			"serial_number": cb.SerialNumber,
		},
	}

	switch format {
	case "pem":
		resp.Data["certificate"] = cb.Certificate
		resp.Data["issuing_ca"] = cb.IssuingCA

	case "pem_bundle":
		resp.Data["certificate"] = fmt.Sprintf("%s\n%s", cb.Certificate, cb.IssuingCA)
		resp.Data["issuing_ca"] = cb.IssuingCA

	case "der":
		resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
		resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.IssuingCABytes)
	}

	err = req.Storage.Put(&logical.StorageEntry{
		Key:   "certs/" + cb.SerialNumber,
		Value: parsedBundle.CertificateBytes,
	})
	if err != nil {
		return nil, fmt.Errorf("Unable to store certificate locally")
	}

	if parsedBundle.Certificate.MaxPathLen == 0 {
		resp.AddWarning("Max path length of the signed certificate is zero. This certificate cannot be used to issue intermediate CA certificates.")
	}

	return resp, nil
}
Пример #15
0
// pathRoleCreateUpdate is used to associate Vault policies to a given AMI ID.
func (b *backend) pathRoleCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {

	roleName := strings.ToLower(data.Get("role").(string))
	if roleName == "" {
		return logical.ErrorResponse("missing role"), nil
	}

	b.roleMutex.Lock()
	defer b.roleMutex.Unlock()

	roleEntry, err := b.nonLockedAWSRole(req.Storage, roleName)
	if err != nil {
		return nil, err
	}
	if roleEntry == nil {
		roleEntry = &awsRoleEntry{}
	}

	// Set the bound parameters only if they are supplied.
	// There are no default values for bound parameters.
	boundAmiIDStr, ok := data.GetOk("bound_ami_id")
	if ok {
		roleEntry.BoundAmiID = boundAmiIDStr.(string)
	}

	boundIamARNStr, ok := data.GetOk("bound_iam_role_arn")
	if ok {
		roleEntry.BoundIamARN = boundIamARNStr.(string)
	}

	// At least one bound parameter should be set. Currently, only
	// 'bound_ami_id' and 'bound_iam_role_arn' are supported. Check if one of them is set.
	if roleEntry.BoundAmiID == "" {
		// check if an IAM Role ARN was provided instead of an AMI ID
		if roleEntry.BoundIamARN == "" {
			return logical.ErrorResponse("role is not bounded to any resource; set bound_ami_id or bount_iam_role_arn"), nil
		}
	}

	policiesStr, ok := data.GetOk("policies")
	if ok {
		roleEntry.Policies = policyutil.ParsePolicies(policiesStr.(string))
	} else if req.Operation == logical.CreateOperation {
		roleEntry.Policies = []string{"default"}
	}

	disallowReauthenticationBool, ok := data.GetOk("disallow_reauthentication")
	if ok {
		roleEntry.DisallowReauthentication = disallowReauthenticationBool.(bool)
	} else if req.Operation == logical.CreateOperation {
		roleEntry.DisallowReauthentication = data.Get("disallow_reauthentication").(bool)
	}

	allowInstanceMigrationBool, ok := data.GetOk("allow_instance_migration")
	if ok {
		roleEntry.AllowInstanceMigration = allowInstanceMigrationBool.(bool)
	} else if req.Operation == logical.CreateOperation {
		roleEntry.AllowInstanceMigration = data.Get("allow_instance_migration").(bool)
	}

	var resp logical.Response

	maxTTLInt, ok := data.GetOk("max_ttl")
	if ok {
		maxTTL := time.Duration(maxTTLInt.(int)) * time.Second
		systemMaxTTL := b.System().MaxLeaseTTL()
		if maxTTL > systemMaxTTL {
			resp.AddWarning(fmt.Sprintf("Given TTL of %d seconds greater than current mount/system default of %d seconds; TTL will be capped at login time", maxTTL/time.Second, systemMaxTTL/time.Second))
		}

		if maxTTL < time.Duration(0) {
			return logical.ErrorResponse("max_ttl cannot be negative"), nil
		}

		roleEntry.MaxTTL = maxTTL
	} else if req.Operation == logical.CreateOperation {
		roleEntry.MaxTTL = time.Duration(data.Get("max_ttl").(int)) * time.Second
	}

	roleTagStr, ok := data.GetOk("role_tag")
	if ok {
		roleEntry.RoleTag = roleTagStr.(string)
		// There is a limit of 127 characters on the tag key for AWS EC2 instances.
		// Complying to that requirement, do not allow the value of 'key' to be more than that.
		if len(roleEntry.RoleTag) > 127 {
			return logical.ErrorResponse("length of role tag exceeds the EC2 key limit of 127 characters"), nil
		}
	} else if req.Operation == logical.CreateOperation {
		roleEntry.RoleTag = data.Get("role_tag").(string)
	}

	if roleEntry.HMACKey == "" {
		roleEntry.HMACKey, err = uuid.GenerateUUID()
		if err != nil {
			return nil, fmt.Errorf("failed to generate role HMAC key: %v", err)
		}
	}

	entry, err := logical.StorageEntryJSON("role/"+roleName, roleEntry)
	if err != nil {
		return nil, err
	}

	if err := req.Storage.Put(entry); err != nil {
		return nil, err
	}

	if len(resp.Warnings()) == 0 {
		return nil, nil
	}

	return &resp, nil
}
Пример #16
0
// pathLoginUpdate is used to create a Vault token by the EC2 instances
// by providing the pkcs7 signature of the instance identity document
// and a client created nonce. Client nonce is optional if 'disallow_reauthentication'
// option is enabled on the registered role.
func (b *backend) pathLoginUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	identityDocB64 := data.Get("identity").(string)
	var identityDocBytes []byte
	var err error
	if identityDocB64 != "" {
		identityDocBytes, err = base64.StdEncoding.DecodeString(identityDocB64)
		if err != nil || len(identityDocBytes) == 0 {
			return logical.ErrorResponse("failed to base64 decode the instance identity document"), nil
		}
	}

	signatureB64 := data.Get("signature").(string)
	var signatureBytes []byte
	if signatureB64 != "" {
		signatureBytes, err = base64.StdEncoding.DecodeString(signatureB64)
		if err != nil {
			return logical.ErrorResponse("failed to base64 decode the SHA256 RSA signature of the instance identity document"), nil
		}
	}

	pkcs7B64 := data.Get("pkcs7").(string)

	// Either the pkcs7 signature of the instance identity document, or
	// the identity document itself along with its SHA256 RSA signature
	// needs to be provided.
	if pkcs7B64 == "" && (len(identityDocBytes) == 0 && len(signatureBytes) == 0) {
		return logical.ErrorResponse("either pkcs7 or a tuple containing the instance identity document and its SHA256 RSA signature needs to be provided"), nil
	} else if pkcs7B64 != "" && (len(identityDocBytes) != 0 && len(signatureBytes) != 0) {
		return logical.ErrorResponse("both pkcs7 and a tuple containing the instance identity document and its SHA256 RSA signature is supplied; provide only one"), nil
	}

	// Verify the signature of the identity document and unmarshal it
	var identityDocParsed *identityDocument
	if pkcs7B64 != "" {
		identityDocParsed, err = b.parseIdentityDocument(req.Storage, pkcs7B64)
		if err != nil {
			return nil, err
		}
		if identityDocParsed == nil {
			return logical.ErrorResponse("failed to verify the instance identity document using pkcs7"), nil
		}
	} else {
		identityDocParsed, err = b.verifyInstanceIdentitySignature(req.Storage, identityDocBytes, signatureBytes)
		if err != nil {
			return nil, err
		}
		if identityDocParsed == nil {
			return logical.ErrorResponse("failed to verify the instance identity document using the SHA256 RSA digest"), nil
		}
	}

	roleName := data.Get("role").(string)

	// If roleName is not supplied, a role in the name of the instance's AMI ID will be looked for
	if roleName == "" {
		roleName = identityDocParsed.AmiID
	}

	// Validate the instance ID by making a call to AWS EC2 DescribeInstances API
	// and fetching the instance description. Validation succeeds only if the
	// instance is in 'running' state.
	instanceDesc, err := b.validateInstance(req.Storage, identityDocParsed.InstanceID, identityDocParsed.Region)
	if err != nil {
		return logical.ErrorResponse(fmt.Sprintf("failed to verify instance ID: %v", err)), nil
	}

	// Get the entry for the role used by the instance
	roleEntry, err := b.lockedAWSRole(req.Storage, roleName)
	if err != nil {
		return nil, err
	}
	if roleEntry == nil {
		return logical.ErrorResponse(fmt.Sprintf("entry for role %q not found", roleName)), nil
	}

	// Verify that the AMI ID of the instance trying to login matches the
	// AMI ID specified as a constraint on the role
	if roleEntry.BoundAmiID != "" && identityDocParsed.AmiID != roleEntry.BoundAmiID {
		return logical.ErrorResponse(fmt.Sprintf("AMI ID %q does not belong to role %q", identityDocParsed.AmiID, roleName)), nil
	}

	// Verify that the AccountID of the instance trying to login matches the
	// AccountID specified as a constraint on the role
	if roleEntry.BoundAccountID != "" && identityDocParsed.AccountID != roleEntry.BoundAccountID {
		return logical.ErrorResponse(fmt.Sprintf("Account ID %q does not belong to role %q", identityDocParsed.AccountID, roleName)), nil
	}

	// Check if the IAM instance profile ARN of the instance trying to
	// login, matches the IAM instance profile ARN specified as a constraint
	// on the role.
	if roleEntry.BoundIamInstanceProfileARN != "" {
		if instanceDesc.Reservations[0].Instances[0].IamInstanceProfile == nil {
			return nil, fmt.Errorf("IAM instance profile in the instance description is nil")
		}
		if instanceDesc.Reservations[0].Instances[0].IamInstanceProfile.Arn == nil {
			return nil, fmt.Errorf("IAM instance profile ARN in the instance description is nil")
		}
		iamInstanceProfileARN := *instanceDesc.Reservations[0].Instances[0].IamInstanceProfile.Arn
		if !strings.HasPrefix(iamInstanceProfileARN, roleEntry.BoundIamInstanceProfileARN) {
			return logical.ErrorResponse(fmt.Sprintf("IAM instance profile ARN %q does not satisfy the constraint role %q", iamInstanceProfileARN, roleName)), nil
		}
	}

	// Check if the IAM role ARN of the instance trying to login, matches
	// the IAM role ARN specified as a constraint on the role.
	if roleEntry.BoundIamRoleARN != "" {
		if instanceDesc.Reservations[0].Instances[0].IamInstanceProfile == nil {
			return nil, fmt.Errorf("IAM instance profile in the instance description is nil")
		}
		if instanceDesc.Reservations[0].Instances[0].IamInstanceProfile.Arn == nil {
			return nil, fmt.Errorf("IAM instance profile ARN in the instance description is nil")
		}

		// Fetch the instance profile ARN from the instance description
		iamInstanceProfileARN := *instanceDesc.Reservations[0].Instances[0].IamInstanceProfile.Arn

		if iamInstanceProfileARN == "" {
			return nil, fmt.Errorf("IAM instance profile ARN in the instance description is empty")
		}

		// Extract out the instance profile name from the instance
		// profile ARN
		iamInstanceProfileARNSlice := strings.SplitAfter(iamInstanceProfileARN, ":instance-profile/")
		iamInstanceProfileName := iamInstanceProfileARNSlice[len(iamInstanceProfileARNSlice)-1]

		if iamInstanceProfileName == "" {
			return nil, fmt.Errorf("failed to extract out IAM instance profile name from IAM instance profile ARN")
		}

		// Use instance profile ARN to fetch the associated role ARN
		iamRoleARN, err := b.instanceIamRoleARN(req.Storage, iamInstanceProfileName, identityDocParsed.Region)
		if err != nil {
			return nil, fmt.Errorf("IAM role ARN could not be fetched: %v", err)
		}
		if iamRoleARN == "" {
			return nil, fmt.Errorf("IAM role ARN could not be fetched")
		}

		if !strings.HasPrefix(iamRoleARN, roleEntry.BoundIamRoleARN) {
			return logical.ErrorResponse(fmt.Sprintf("IAM role ARN %q does not satisfy the constraint role %q", iamRoleARN, roleName)), nil
		}
	}

	// Get the entry from the identity whitelist, if there is one
	storedIdentity, err := whitelistIdentityEntry(req.Storage, identityDocParsed.InstanceID)
	if err != nil {
		return nil, err
	}

	// disallowReauthentication value that gets cached at the stored
	// identity-whitelist entry is determined not just by the role entry.
	// If client explicitly sets nonce to be empty, it implies intent to
	// disable reauthentication. Also, role tag can override the 'false'
	// value with 'true' (the other way around is not allowed).

	// Read the value from the role entry
	disallowReauthentication := roleEntry.DisallowReauthentication

	clientNonce := ""

	// Check if the nonce is supplied by the client
	clientNonceRaw, clientNonceSupplied := data.GetOk("nonce")
	if clientNonceSupplied {
		clientNonce = clientNonceRaw.(string)

		// Nonce explicitly set to empty implies intent to disable
		// reauthentication by the client. Set a predefined nonce which
		// indicates reauthentication being disabled.
		if clientNonce == "" {
			clientNonce = reauthenticationDisabledNonce

			// Ensure that the intent lands in the whitelist
			disallowReauthentication = true
		}
	}

	// This is NOT a first login attempt from the client
	if storedIdentity != nil {
		// Check if the client nonce match the cached nonce and if the pending time
		// of the identity document is not before the pending time of the document
		// with which previous login was made. If 'allow_instance_migration' is
		// enabled on the registered role, client nonce requirement is relaxed.
		if err = validateMetadata(clientNonce, identityDocParsed.PendingTime, storedIdentity, roleEntry); err != nil {
			return logical.ErrorResponse(err.Error()), nil
		}

		// Don't let subsequent login attempts to bypass in initial
		// intent of disabling reauthentication, despite the properties
		// of role getting updated. For example: Role has the value set
		// to 'false', a role-tag login sets the value to 'true', then
		// role gets updated to not use a role-tag, and a login attempt
		// is made with role's value set to 'false'. Removing the entry
		// from the identity-whitelist should be the only way to be
		// able to login from the instance again.
		disallowReauthentication = disallowReauthentication || storedIdentity.DisallowReauthentication
	}

	// If we reach this point without erroring and if the client nonce was
	// not supplied, a first time login is implied and that the client
	// intends that the nonce be generated by the backend. Create a random
	// nonce to be associated for the instance ID.
	if !clientNonceSupplied {
		if clientNonce, err = uuid.GenerateUUID(); err != nil {
			return nil, fmt.Errorf("failed to generate random nonce")
		}
	}

	// Load the current values for max TTL and policies from the role entry,
	// before checking for overriding max TTL in the role tag.  The shortest
	// max TTL is used to cap the token TTL; the longest max TTL is used to
	// make the whitelist entry as long as possible as it controls for replay
	// attacks.
	shortestMaxTTL := b.System().MaxLeaseTTL()
	longestMaxTTL := b.System().MaxLeaseTTL()
	if roleEntry.MaxTTL > time.Duration(0) && roleEntry.MaxTTL < shortestMaxTTL {
		shortestMaxTTL = roleEntry.MaxTTL
	}
	if roleEntry.MaxTTL > longestMaxTTL {
		longestMaxTTL = roleEntry.MaxTTL
	}

	policies := roleEntry.Policies
	rTagMaxTTL := time.Duration(0)

	if roleEntry.RoleTag != "" {
		//
		// Role tag is enabled on the role.
		//

		// Overwrite the policies with the ones returned from processing the role tag
		resp, err := b.handleRoleTagLogin(req.Storage, identityDocParsed, roleName, roleEntry, instanceDesc)
		if err != nil {
			return nil, err
		}
		if resp == nil {
			return logical.ErrorResponse("failed to fetch and verify the role tag"), nil
		}

		// If there are no policies on the role tag, policies on the role are inherited.
		// If policies on role tag are set, by this point, it is verified that it is a subset of the
		// policies on the role. So, apply only those.
		if len(resp.Policies) != 0 {
			policies = resp.Policies
		}

		// If roleEntry had disallowReauthentication set to 'true', do not reset it
		// to 'false' based on role tag having it not set. But, if role tag had it set,
		// be sure to override the value.
		if !disallowReauthentication {
			disallowReauthentication = resp.DisallowReauthentication
		}

		// Cache the value of role tag's max_ttl value
		rTagMaxTTL = resp.MaxTTL

		// Scope the shortestMaxTTL to the value set on the role tag
		if resp.MaxTTL > time.Duration(0) && resp.MaxTTL < shortestMaxTTL {
			shortestMaxTTL = resp.MaxTTL
		}
		if resp.MaxTTL > longestMaxTTL {
			longestMaxTTL = resp.MaxTTL
		}
	}

	// Save the login attempt in the identity whitelist
	currentTime := time.Now()
	if storedIdentity == nil {
		// Role, ClientNonce and CreationTime of the identity entry,
		// once set, should never change.
		storedIdentity = &whitelistIdentity{
			Role:         roleName,
			ClientNonce:  clientNonce,
			CreationTime: currentTime,
		}
	}

	// DisallowReauthentication, PendingTime, LastUpdatedTime and
	// ExpirationTime may change.
	storedIdentity.LastUpdatedTime = currentTime
	storedIdentity.ExpirationTime = currentTime.Add(longestMaxTTL)
	storedIdentity.PendingTime = identityDocParsed.PendingTime
	storedIdentity.DisallowReauthentication = disallowReauthentication

	// Don't cache the nonce if DisallowReauthentication is set
	if storedIdentity.DisallowReauthentication {
		storedIdentity.ClientNonce = ""
	}

	// Sanitize the nonce to a reasonable length
	if len(clientNonce) > 128 && !storedIdentity.DisallowReauthentication {
		return logical.ErrorResponse("client nonce exceeding the limit of 128 characters"), nil
	}

	if err = setWhitelistIdentityEntry(req.Storage, identityDocParsed.InstanceID, storedIdentity); err != nil {
		return nil, err
	}

	resp := &logical.Response{
		Auth: &logical.Auth{
			Policies: policies,
			Metadata: map[string]string{
				"instance_id":      identityDocParsed.InstanceID,
				"region":           identityDocParsed.Region,
				"role_tag_max_ttl": rTagMaxTTL.String(),
				"role":             roleName,
				"ami_id":           identityDocParsed.AmiID,
			},
			LeaseOptions: logical.LeaseOptions{
				Renewable: true,
				TTL:       roleEntry.TTL,
			},
		},
	}

	// Return the nonce only if reauthentication is allowed
	if !disallowReauthentication {
		// Echo the client nonce back. If nonce param was not supplied
		// to the endpoint at all (setting it to empty string does not
		// qualify here), callers should extract out the nonce from
		// this field for reauthentication requests.
		resp.Auth.Metadata["nonce"] = clientNonce
	}

	// Cap the TTL value.
	shortestTTL := b.System().DefaultLeaseTTL()
	if roleEntry.TTL > time.Duration(0) && roleEntry.TTL < shortestTTL {
		shortestTTL = roleEntry.TTL
	}
	if shortestMaxTTL < shortestTTL {
		resp.AddWarning(fmt.Sprintf("Effective ttl of %q exceeded the effective max_ttl of %q; ttl value is capped appropriately", (shortestTTL / time.Second).String(), (shortestMaxTTL / time.Second).String()))
		shortestTTL = shortestMaxTTL
	}
	resp.Auth.TTL = shortestTTL

	return resp, nil

}
Пример #17
0
// pathRoleTagUpdate is used to create an EC2 instance tag which will
// identify the Vault resources that the instance will be authorized for.
func (b *backend) pathRoleTagUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {

	roleName := strings.ToLower(data.Get("role").(string))
	if roleName == "" {
		return logical.ErrorResponse("missing role"), nil
	}

	// Fetch the role entry
	roleEntry, err := b.lockedAWSRole(req.Storage, roleName)
	if err != nil {
		return nil, err
	}
	if roleEntry == nil {
		return logical.ErrorResponse(fmt.Sprintf("entry not found for role %s", roleName)), nil
	}

	// If RoleTag is empty, disallow creation of tag.
	if roleEntry.RoleTag == "" {
		return logical.ErrorResponse("tag creation is not enabled for this role"), nil
	}

	// There should be a HMAC key present in the role entry
	if roleEntry.HMACKey == "" {
		// Not being able to find the HMACKey is an internal error
		return nil, fmt.Errorf("failed to find the HMAC key")
	}

	resp := &logical.Response{}

	// Instance ID is an optional field.
	instanceID := strings.ToLower(data.Get("instance_id").(string))

	// If no policies field was not supplied, then the tag should inherit all the policies
	// on the role. But, it was provided, but set to empty explicitly, only "default" policy
	// should be inherited. So, by leaving the policies var unset to anything when it is not
	// supplied, we ensure that it inherits all the policies on the role.
	var policies []string
	policiesStr, ok := data.GetOk("policies")
	if ok {
		policies = policyutil.ParsePolicies(policiesStr.(string))
	}
	if !strutil.StrListSubset(roleEntry.Policies, policies) {
		resp.AddWarning("Policies on the tag are not a subset of the policies set on the role. Login will not be allowed with this tag unless the role policies are updated.")
	}

	// This is an optional field.
	disallowReauthentication := data.Get("disallow_reauthentication").(bool)

	// This is an optional field.
	allowInstanceMigration := data.Get("allow_instance_migration").(bool)
	if allowInstanceMigration && !roleEntry.AllowInstanceMigration {
		resp.AddWarning("Role does not allow instance migration. Login will not be allowed with this tag unless the role value is updated.")
	}

	// max_ttl for the role tag should be less than the max_ttl set on the role.
	maxTTL := time.Duration(data.Get("max_ttl").(int)) * time.Second

	// max_ttl on the tag should not be greater than the system view's max_ttl value.
	if maxTTL > b.System().MaxLeaseTTL() {
		resp.AddWarning(fmt.Sprintf("Given max TTL of %d is greater than the mount maximum of %d seconds, and will be capped at login time.", maxTTL/time.Second, b.System().MaxLeaseTTL()/time.Second))
	}
	// If max_ttl is set for the role, check the bounds for tag's max_ttl value using that.
	if roleEntry.MaxTTL != time.Duration(0) && maxTTL > roleEntry.MaxTTL {
		resp.AddWarning(fmt.Sprintf("Given max TTL of %d is greater than the role maximum of %d seconds, and will be capped at login time.", maxTTL/time.Second, roleEntry.MaxTTL/time.Second))
	}

	if maxTTL < time.Duration(0) {
		return logical.ErrorResponse("max_ttl cannot be negative"), nil
	}

	// Create a random nonce.
	nonce, err := createRoleTagNonce()
	if err != nil {
		return nil, err
	}

	// Create a role tag out of all the information provided.
	rTagValue, err := createRoleTagValue(&roleTag{
		Version:                  roleTagVersion,
		Role:                     roleName,
		Nonce:                    nonce,
		Policies:                 policies,
		MaxTTL:                   maxTTL,
		InstanceID:               instanceID,
		DisallowReauthentication: disallowReauthentication,
		AllowInstanceMigration:   allowInstanceMigration,
	}, roleEntry)
	if err != nil {
		return nil, err
	}

	// Return the key to be used for the tag and the value to be used for that tag key.
	// This key value pair should be set on the EC2 instance.
	resp.Data = map[string]interface{}{
		"tag_key":   roleEntry.RoleTag,
		"tag_value": rTagValue,
	}

	return resp, nil
}
Пример #18
0
func (b *backend) pathCAGenerateRoot(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	var err error

	exported, format, role, errorResp := b.getGenerationParams(data)
	if errorResp != nil {
		return errorResp, nil
	}

	maxPathLengthIface, ok := data.GetOk("max_path_length")
	if ok {
		maxPathLength := maxPathLengthIface.(int)
		role.MaxPathLength = &maxPathLength
	}

	parsedBundle, err := generateCert(b, role, nil, true, req, data)
	if err != nil {
		switch err.(type) {
		case certutil.UserError:
			return logical.ErrorResponse(err.Error()), nil
		case certutil.InternalError:
			return nil, err
		}
	}

	cb, err := parsedBundle.ToCertBundle()
	if err != nil {
		return nil, fmt.Errorf("error converting raw cert bundle to cert bundle: %s", err)
	}

	resp := &logical.Response{
		Data: map[string]interface{}{
			"expiration":    int64(parsedBundle.Certificate.NotAfter.Unix()),
			"serial_number": cb.SerialNumber,
		},
	}

	switch format {
	case "pem":
		resp.Data["certificate"] = cb.Certificate
		resp.Data["issuing_ca"] = cb.IssuingCA
		if exported {
			resp.Data["private_key"] = cb.PrivateKey
			resp.Data["private_key_type"] = cb.PrivateKeyType
		}

	case "pem_bundle":
		resp.Data["issuing_ca"] = cb.IssuingCA

		if exported {
			resp.Data["private_key"] = cb.PrivateKey
			resp.Data["private_key_type"] = cb.PrivateKeyType
			resp.Data["certificate"] = fmt.Sprintf("%s\n%s\n%s", cb.PrivateKey, cb.Certificate, cb.IssuingCA)
		} else {
			resp.Data["certificate"] = fmt.Sprintf("%s\n%s", cb.Certificate, cb.IssuingCA)
		}

	case "der":
		resp.Data["certificate"] = base64.StdEncoding.EncodeToString(parsedBundle.CertificateBytes)
		resp.Data["issuing_ca"] = base64.StdEncoding.EncodeToString(parsedBundle.IssuingCABytes)
		if exported {
			resp.Data["private_key"] = base64.StdEncoding.EncodeToString(parsedBundle.PrivateKeyBytes)
			resp.Data["private_key_type"] = cb.PrivateKeyType
		}
	}

	// Store it as the CA bundle
	entry, err := logical.StorageEntryJSON("config/ca_bundle", cb)
	if err != nil {
		return nil, err
	}
	err = req.Storage.Put(entry)
	if err != nil {
		return nil, err
	}

	// Also store it as just the certificate identified by serial number, so it
	// can be revoked
	err = req.Storage.Put(&logical.StorageEntry{
		Key:   "certs/" + cb.SerialNumber,
		Value: parsedBundle.CertificateBytes,
	})
	if err != nil {
		return nil, fmt.Errorf("Unable to store certificate locally")
	}

	// For ease of later use, also store just the certificate at a known
	// location
	entry.Key = "ca"
	entry.Value = parsedBundle.CertificateBytes
	err = req.Storage.Put(entry)
	if err != nil {
		return nil, err
	}

	// Build a fresh CRL
	err = buildCRL(b, req)
	if err != nil {
		return nil, err
	}

	if parsedBundle.Certificate.MaxPathLen == 0 {
		resp.AddWarning("Max path length of the generated certificate is zero. This certificate cannot be used to issue intermediate CA certificates.")
	}

	return resp, nil
}
Пример #19
0
func pathConfigWrite(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	name := d.Get("name").(string)

	// Check if the policy already exists
	policy, err := getPolicy(req, name)
	if err != nil {
		return nil, err
	}
	if policy == nil {
		return logical.ErrorResponse(
				fmt.Sprintf("no existing role named %s could be found", name)),
			logical.ErrInvalidRequest
	}

	resp := &logical.Response{}

	persistNeeded := false

	minDecryptionVersionRaw, ok := d.GetOk("min_decryption_version")
	if ok {
		minDecryptionVersion := minDecryptionVersionRaw.(int)

		if minDecryptionVersion < 0 {
			return logical.ErrorResponse("min decryption version cannot be negative"), nil
		}

		if minDecryptionVersion == 0 {
			minDecryptionVersion = 1
			resp.AddWarning("since Vault 0.3, transit key numbering starts at 1; forcing minimum to 1")
		}

		if minDecryptionVersion > 0 &&
			minDecryptionVersion != policy.MinDecryptionVersion {
			if minDecryptionVersion > policy.LatestVersion {
				return logical.ErrorResponse(
					fmt.Sprintf("cannot set min decryption version of %d, latest key version is %d", minDecryptionVersion, policy.LatestVersion)), nil
			}
			policy.MinDecryptionVersion = minDecryptionVersion
			persistNeeded = true
		}
	}

	allowDeletionInt, ok := d.GetOk("deletion_allowed")
	if ok {
		allowDeletion := allowDeletionInt.(bool)
		if allowDeletion != policy.DeletionAllowed {
			policy.DeletionAllowed = allowDeletion
			persistNeeded = true
		}
	}

	// Add this as a guard here before persisting since we now require the min
	// decryption version to start at 1; even if it's not explicitly set here,
	// force the upgrade
	if policy.MinDecryptionVersion == 0 {
		policy.MinDecryptionVersion = 1
		persistNeeded = true
	}

	if !persistNeeded {
		return nil, nil
	}

	return resp, policy.Persist(req.Storage)
}
Пример #20
0
func (ts *TokenStore) tokenStoreRoleCreateUpdate(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	name := data.Get("role_name").(string)
	if name == "" {
		return logical.ErrorResponse("role name cannot be empty"), nil
	}
	entry, err := ts.tokenStoreRole(name)
	if err != nil {
		return nil, err
	}

	// Due to the existence check, entry will only be nil if it's a create
	// operation, so just create a new one
	if entry == nil {
		entry = &tsRoleEntry{
			Name: name,
		}
	}

	// In this series of blocks, if we do not find a user-provided value and
	// it's a creation operation, we call data.Get to get the appropriate
	// default

	orphanInt, ok := data.GetOk("orphan")
	if ok {
		entry.Orphan = orphanInt.(bool)
	} else if req.Operation == logical.CreateOperation {
		entry.Orphan = data.Get("orphan").(bool)
	}

	periodInt, ok := data.GetOk("period")
	if ok {
		entry.Period = time.Second * time.Duration(periodInt.(int))
	} else if req.Operation == logical.CreateOperation {
		entry.Period = time.Second * time.Duration(data.Get("period").(int))
	}

	var resp *logical.Response

	explicitMaxTTLInt, ok := data.GetOk("explicit_max_ttl")
	if ok {
		entry.ExplicitMaxTTL = time.Second * time.Duration(explicitMaxTTLInt.(int))
	} else if req.Operation == logical.CreateOperation {
		entry.ExplicitMaxTTL = time.Second * time.Duration(data.Get("explicit_max_ttl").(int))
	}
	if entry.ExplicitMaxTTL != 0 {
		sysView := ts.System()

		if sysView.MaxLeaseTTL() != time.Duration(0) && entry.ExplicitMaxTTL > sysView.MaxLeaseTTL() {
			if resp == nil {
				resp = &logical.Response{}
			}
			resp.AddWarning(fmt.Sprintf(
				"Given explicit max TTL of %d is greater than system/mount allowed value of %d seconds; until this is fixed attempting to create tokens against this role will result in an error",
				entry.ExplicitMaxTTL.Seconds(), sysView.MaxLeaseTTL().Seconds()))
		}
	}

	pathSuffixInt, ok := data.GetOk("path_suffix")
	if ok {
		pathSuffix := pathSuffixInt.(string)
		if pathSuffix != "" {
			matched := pathSuffixSanitize.MatchString(pathSuffix)
			if !matched {
				return logical.ErrorResponse(fmt.Sprintf(
					"given role path suffix contains invalid characters; must match %s",
					pathSuffixSanitize.String())), nil
			}
			entry.PathSuffix = pathSuffix
		}
	} else if req.Operation == logical.CreateOperation {
		entry.PathSuffix = data.Get("path_suffix").(string)
	}

	allowedPoliciesInt, ok := data.GetOk("allowed_policies")
	if ok {
		allowedPolicies := allowedPoliciesInt.(string)
		if allowedPolicies != "" {
			entry.AllowedPolicies = strings.Split(allowedPolicies, ",")
		}
	} else if req.Operation == logical.CreateOperation {
		entry.AllowedPolicies = strings.Split(data.Get("allowed_policies").(string), ",")
	}

	// Explicit max TTLs and periods cannot be used at the same time since the
	// purpose of a periodic token is to escape max TTL semantics
	if entry.Period > 0 && entry.ExplicitMaxTTL > 0 {
		return logical.ErrorResponse("a role cannot be used to issue both periodic tokens and tokens with explicit max TTLs"), logical.ErrInvalidRequest
	}

	// Store it
	jsonEntry, err := logical.StorageEntryJSON(fmt.Sprintf("%s%s", rolesPrefix, name), entry)
	if err != nil {
		return nil, err
	}
	if err := ts.view.Put(jsonEntry); err != nil {
		return nil, err
	}

	return resp, nil
}