Example #1
0
// 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
}
Example #2
0
// 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
}