// handleRoleTagLogin is used to fetch the role tag of the instance and verifies it to be correct. // Then the policies for the login request will be set off of the role tag, if certain creteria satisfies. func (b *backend) handleRoleTagLogin(s logical.Storage, identityDoc *identityDocument, roleName string, roleEntry *awsRoleEntry, instanceDesc *ec2.DescribeInstancesOutput) (*roleTagLoginResponse, error) { if identityDoc == nil { return nil, fmt.Errorf("nil identityDoc") } if roleEntry == nil { return nil, fmt.Errorf("nil roleEntry") } if instanceDesc == nil { return nil, fmt.Errorf("nil instanceDesc") } // Input validation on instanceDesc is not performed here considering // that it would have been done in validateInstance method. tags := instanceDesc.Reservations[0].Instances[0].Tags if tags == nil || len(tags) == 0 { return nil, fmt.Errorf("missing tag with key %s on the instance", roleEntry.RoleTag) } // Iterate through the tags attached on the instance and look for // a tag with its 'key' matching the expected role tag value. rTagValue := "" for _, tagItem := range tags { if tagItem.Key != nil && *tagItem.Key == roleEntry.RoleTag { rTagValue = *tagItem.Value break } } // If 'role_tag' is enabled on the role, and if a corresponding tag is not found // to be attached to the instance, fail. if rTagValue == "" { return nil, fmt.Errorf("missing tag with key %s on the instance", roleEntry.RoleTag) } // Parse the role tag into a struct, extract the plaintext part of it and verify its HMAC. rTag, err := b.parseAndVerifyRoleTagValue(s, rTagValue) if err != nil { return nil, err } // Check if the role name with which this login is being made is same // as the role name embedded in the tag. if rTag.Role != roleName { return nil, fmt.Errorf("role on the tag is not matching the role supplied") } // If instance_id was set on the role tag, check if the same instance is attempting to login. if rTag.InstanceID != "" && rTag.InstanceID != identityDoc.InstanceID { return nil, fmt.Errorf("role tag is being used by an unauthorized instance.") } // Check if the role tag is blacklisted. blacklistEntry, err := b.lockedBlacklistRoleTagEntry(s, rTagValue) if err != nil { return nil, err } if blacklistEntry != nil { return nil, fmt.Errorf("role tag is blacklisted") } // Ensure that the policies on the RoleTag is a subset of policies on the role if !strutil.StrListSubset(roleEntry.Policies, rTag.Policies) { return nil, fmt.Errorf("policies on the role tag must be subset of policies on the role") } return &roleTagLoginResponse{ Policies: rTag.Policies, MaxTTL: rTag.MaxTTL, DisallowReauthentication: rTag.DisallowReauthentication, }, nil }
// handleLoginRequest is used to handle a login request, which is an // unauthenticated request to the backend. func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *logical.Auth, error) { defer metrics.MeasureSince([]string{"core", "handle_login_request"}, time.Now()) // Create an audit trail of the request, auth is not available on login requests if err := c.auditBroker.LogRequest(nil, req, nil); err != nil { c.logger.Printf("[ERR] core: failed to audit request with path %s: %v", req.Path, err) return nil, nil, ErrInternalError } // Route the request resp, err := c.router.Route(req) // A login request should never return a secret! if resp != nil && resp.Secret != nil { c.logger.Printf("[ERR] core: unexpected Secret response for login path"+ "(request path: %s)", req.Path) return nil, nil, ErrInternalError } // If the response generated an authentication, then generate the token var auth *logical.Auth if resp != nil && resp.Auth != nil { auth = resp.Auth // Determine the source of the login source := c.router.MatchingMount(req.Path) source = strings.TrimPrefix(source, credentialRoutePrefix) source = strings.Replace(source, "/", "-", -1) // Prepend the source to the display name auth.DisplayName = strings.TrimSuffix(source+auth.DisplayName, "-") sysView := c.router.MatchingSystemView(req.Path) if sysView == nil { c.logger.Printf("[ERR] core: unable to look up sys view for login path"+ "(request path: %s)", req.Path) return nil, nil, ErrInternalError } // Set the default lease if non-provided, root tokens are exempt if auth.TTL == 0 && !strutil.StrListContains(auth.Policies, "root") { auth.TTL = sysView.DefaultLeaseTTL() } // Limit the lease duration if auth.TTL > sysView.MaxLeaseTTL() { auth.TTL = sysView.MaxLeaseTTL() } // Generate a token te := TokenEntry{ Path: req.Path, Policies: auth.Policies, Meta: auth.Metadata, DisplayName: auth.DisplayName, CreationTime: time.Now().Unix(), TTL: auth.TTL, } if strutil.StrListSubset(te.Policies, []string{"root"}) { te.Policies = []string{"root"} } else { // Use a map to filter out/prevent duplicates policyMap := map[string]bool{} for _, policy := range te.Policies { if policy == "" { // Don't allow a policy with no name, even though it is a valid // slice member continue } policyMap[policy] = true } // Add the default policy policyMap["default"] = true te.Policies = []string{} for k, _ := range policyMap { te.Policies = append(te.Policies, k) } sort.Strings(te.Policies) } if err := c.tokenStore.create(&te); err != nil { c.logger.Printf("[ERR] core: failed to create token: %v", err) return nil, auth, ErrInternalError } // Populate the client token and accessor auth.ClientToken = te.ID auth.Accessor = te.Accessor auth.Policies = te.Policies // Register with the expiration manager if err := c.expiration.RegisterAuth(te.Path, auth); err != nil { c.logger.Printf("[ERR] core: failed to register token lease "+ "(request path: %s): %v", req.Path, err) return nil, auth, ErrInternalError } // Attach the display name, might be used by audit backends req.DisplayName = auth.DisplayName } return resp, auth, err }
// handleCreateCommon handles the auth/token/create path for creation of new tokens func (ts *TokenStore) handleCreateCommon( req *logical.Request, d *framework.FieldData, orphan bool, role *tsRoleEntry) (*logical.Response, error) { // Read the parent policy parent, err := ts.Lookup(req.ClientToken) if err != nil || parent == nil { return logical.ErrorResponse("parent token lookup failed"), logical.ErrInvalidRequest } // A token with a restricted number of uses cannot create a new token // otherwise it could escape the restriction count. if parent.NumUses > 0 { return logical.ErrorResponse("restricted use token cannot generate child tokens"), logical.ErrInvalidRequest } // Check if the client token has sudo/root privileges for the requested path isSudo := ts.System().SudoPrivilege(req.MountPoint+req.Path, req.ClientToken) // Read and parse the fields var data struct { ID string Policies []string Metadata map[string]string `mapstructure:"meta"` NoParent bool `mapstructure:"no_parent"` NoDefaultPolicy bool `mapstructure:"no_default_policy"` Lease string TTL string DisplayName string `mapstructure:"display_name"` NumUses int `mapstructure:"num_uses"` } if err := mapstructure.WeakDecode(req.Data, &data); err != nil { return logical.ErrorResponse(fmt.Sprintf( "Error decoding request: %s", err)), logical.ErrInvalidRequest } // Verify the number of uses is positive if data.NumUses < 0 { return logical.ErrorResponse("number of uses cannot be negative"), logical.ErrInvalidRequest } // Setup the token entry te := TokenEntry{ Parent: req.ClientToken, // The mount point is always the same since we have only one token // store; using req.MountPoint causes trouble in tests since they don't // have an official mount Path: fmt.Sprintf("auth/token/%s", req.Path), Meta: data.Metadata, DisplayName: "token", NumUses: data.NumUses, CreationTime: time.Now().Unix(), } // If the role is not nil, we add the role name as part of the token's // path. This makes it much easier to later revoke tokens that were issued // by a role (using revoke-prefix). Users can further specify a PathSuffix // in the role; that way they can use something like "v1", "v2" to indicate // role revisions, and revoke only tokens issued with a previous revision. if role != nil { te.Role = role.Name if role.PathSuffix != "" { te.Path = fmt.Sprintf("%s/%s", te.Path, role.PathSuffix) } } // Attach the given display name if any if data.DisplayName != "" { full := "token-" + data.DisplayName full = displayNameSanitize.ReplaceAllString(full, "-") full = strings.TrimSuffix(full, "-") te.DisplayName = full } // Allow specifying the ID of the token if the client has root or sudo privileges if data.ID != "" { if !isSudo { return logical.ErrorResponse("root or sudo privileges required to specify token id"), logical.ErrInvalidRequest } te.ID = data.ID } switch { // If we have a role, and the role defines policies, we don't even consider // parent policies; the role allowed policies trumps all case role != nil && len(role.AllowedPolicies) > 0: if len(data.Policies) == 0 { data.Policies = role.AllowedPolicies } else { if !strutil.StrListSubset(role.AllowedPolicies, data.Policies) { return logical.ErrorResponse("token policies must be subset of the role's allowed policies"), logical.ErrInvalidRequest } } case len(data.Policies) == 0: data.Policies = parent.Policies // When a role is not in use, only permit policies to be a subset unless // the client has root or sudo privileges case !isSudo && !strutil.StrListSubset(parent.Policies, data.Policies): return logical.ErrorResponse("child policies must be subset of parent"), logical.ErrInvalidRequest } // Use a map to filter out/prevent duplicates policyMap := map[string]bool{} for _, policy := range data.Policies { if policy == "" { // Don't allow a policy with no name, even though it is a valid // slice member continue } policyMap[policy] = true } if !policyMap["root"] && !data.NoDefaultPolicy { policyMap["default"] = true } for k, _ := range policyMap { te.Policies = append(te.Policies, k) } sort.Strings(te.Policies) switch { case role != nil: if role.Orphan { te.Parent = "" } case data.NoParent: // Only allow an orphan token if the client has sudo policy if !isSudo { return logical.ErrorResponse("root or sudo privileges required to create orphan token"), logical.ErrInvalidRequest } te.Parent = "" default: // This comes from create-orphan, which can be properly ACLd if orphan { te.Parent = "" } } if role != nil && role.Period > 0 { te.TTL = role.Period } else { // Parse the TTL/lease if any if data.TTL != "" { dur, err := time.ParseDuration(data.TTL) if err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } if dur < 0 { return logical.ErrorResponse("ttl must be positive"), logical.ErrInvalidRequest } te.TTL = dur } else if data.Lease != "" { dur, err := time.ParseDuration(data.Lease) if err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } if dur < 0 { return logical.ErrorResponse("lease must be positive"), logical.ErrInvalidRequest } te.TTL = dur } sysView := ts.System() // Set the default lease if non-provided, root tokens are exempt if te.TTL == 0 && !strutil.StrListContains(te.Policies, "root") { te.TTL = sysView.DefaultLeaseTTL() } // Limit the lease duration if te.TTL > sysView.MaxLeaseTTL() && sysView.MaxLeaseTTL() != time.Duration(0) { te.TTL = sysView.MaxLeaseTTL() } } // Create the token if err := ts.create(&te); err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } // Generate the response resp := &logical.Response{ Auth: &logical.Auth{ DisplayName: te.DisplayName, Policies: te.Policies, Metadata: te.Meta, LeaseOptions: logical.LeaseOptions{ TTL: te.TTL, Renewable: true, }, ClientToken: te.ID, Accessor: te.Accessor, }, } if ts.policyLookupFunc != nil { for _, p := range te.Policies { policy, err := ts.policyLookupFunc(p) if err != nil { return logical.ErrorResponse(fmt.Sprintf("could not look up policy %s", p)), nil } if policy == nil { resp.AddWarning(fmt.Sprintf("policy \"%s\" does not exist", p)) } } } return resp, nil }
// 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 }
// handleLoginRequest is used to handle a login request, which is an // unauthenticated request to the backend. func (c *Core) handleLoginRequest(req *logical.Request) (*logical.Response, *logical.Auth, error) { defer metrics.MeasureSince([]string{"core", "handle_login_request"}, time.Now()) // Create an audit trail of the request, auth is not available on login requests if err := c.auditBroker.LogRequest(nil, req, nil); err != nil { c.logger.Error("core: failed to audit request", "path", req.Path, "error", err) return nil, nil, ErrInternalError } // The token store uses authentication even when creating a new token, // so it's handled in handleRequest. It should not be reached here. if strings.HasPrefix(req.Path, "auth/token/") { c.logger.Error("core: unexpected login request for token backend", "request_path", req.Path) return nil, nil, ErrInternalError } // Route the request resp, err := c.router.Route(req) if resp != nil { // We don't allow backends to specify this, so ensure it's not set resp.WrapInfo = nil if req.WrapTTL != 0 { resp.WrapInfo = &logical.WrapInfo{ TTL: req.WrapTTL, } } } // A login request should never return a secret! if resp != nil && resp.Secret != nil { c.logger.Error("core: unexpected Secret response for login path", "request_path", req.Path) return nil, nil, ErrInternalError } // If the response generated an authentication, then generate the token var auth *logical.Auth if resp != nil && resp.Auth != nil { auth = resp.Auth if strutil.StrListSubset(auth.Policies, []string{"root"}) { return logical.ErrorResponse("authentication backends cannot create root tokens"), nil, logical.ErrInvalidRequest } // Determine the source of the login source := c.router.MatchingMount(req.Path) source = strings.TrimPrefix(source, credentialRoutePrefix) source = strings.Replace(source, "/", "-", -1) // Prepend the source to the display name auth.DisplayName = strings.TrimSuffix(source+auth.DisplayName, "-") sysView := c.router.MatchingSystemView(req.Path) if sysView == nil { c.logger.Error("core: unable to look up sys view for login path", "request_path", req.Path) return nil, nil, ErrInternalError } // Set the default lease if not provided if auth.TTL == 0 { auth.TTL = sysView.DefaultLeaseTTL() } // Limit the lease duration if auth.TTL > sysView.MaxLeaseTTL() { auth.TTL = sysView.MaxLeaseTTL() } // Generate a token te := TokenEntry{ Path: req.Path, Policies: auth.Policies, Meta: auth.Metadata, DisplayName: auth.DisplayName, CreationTime: time.Now().Unix(), TTL: auth.TTL, } te.Policies = policyutil.SanitizePolicies(te.Policies, true) // Prevent internal policies from being assigned to tokens for _, policy := range te.Policies { if strutil.StrListContains(nonAssignablePolicies, policy) { return logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), nil, logical.ErrInvalidRequest } } if err := c.tokenStore.create(&te); err != nil { c.logger.Error("core: failed to create token", "error", err) return nil, auth, ErrInternalError } // Populate the client token and accessor auth.ClientToken = te.ID auth.Accessor = te.Accessor auth.Policies = te.Policies // Register with the expiration manager if err := c.expiration.RegisterAuth(te.Path, auth); err != nil { c.logger.Error("core: failed to register token lease", "request_path", req.Path, "error", err) return nil, auth, ErrInternalError } // Attach the display name, might be used by audit backends req.DisplayName = auth.DisplayName } return resp, auth, err }