// 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 }
// 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 { // Sanitize passed-in and role policies before comparison sanitizedInputPolicies := policyutil.SanitizePolicies(data.Policies) sanitizedRolePolicies := policyutil.SanitizePolicies(role.AllowedPolicies) if !strutil.StrListSubset(sanitizedRolePolicies, sanitizedInputPolicies) { return logical.ErrorResponse(fmt.Sprintf("token policies (%v) must be subset of the role's allowed policies (%v)", sanitizedInputPolicies, sanitizedRolePolicies)), 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: // Sanitize passed-in and parent policies before comparison sanitizedInputPolicies := policyutil.SanitizePolicies(data.Policies) sanitizedParentPolicies := policyutil.SanitizePolicies(parent.Policies) if !strutil.StrListSubset(sanitizedParentPolicies, sanitizedInputPolicies) { 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 != "" { // This block is compatibility 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() } } resp := &logical.Response{} if role != nil && role.ExplicitMaxTTL != 0 { sysView := ts.System() // Limit the lease duration if sysView.MaxLeaseTTL() != time.Duration(0) && role.ExplicitMaxTTL > sysView.MaxLeaseTTL() { return logical.ErrorResponse(fmt.Sprintf( "role explicit max TTL of %d is greater than system/mount allowed value of %d seconds", role.ExplicitMaxTTL.Seconds(), sysView.MaxLeaseTTL().Seconds())), logical.ErrInvalidRequest } if te.TTL > role.ExplicitMaxTTL { resp.AddWarning(fmt.Sprintf( "Requested TTL higher than role explicit max TTL; value being capped to %d seconds", role.ExplicitMaxTTL.Seconds())) te.TTL = role.ExplicitMaxTTL } te.ExplicitMaxTTL = role.ExplicitMaxTTL } // Create the token if err := ts.create(&te); err != nil { return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest } // Generate the response resp.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 }