func (b *PassthroughBackend) handleRead(
	req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
	// Read the path
	out, err := req.Storage.Get(req.Path)
	if err != nil {
		return nil, fmt.Errorf("read failed: %v", err)
	}

	// Fast-path the no data case
	if out == nil {
		return nil, nil
	}

	// Decode the data
	var rawData map[string]interface{}
	if err := json.Unmarshal(out.Value, &rawData); err != nil {
		return nil, fmt.Errorf("json decoding failed: %v", err)
	}

	var resp *logical.Response
	if b.generateLeases {
		// Generate the response
		resp = b.Secret("generic").Response(rawData, nil)
		resp.Secret.Renewable = false
	} else {
		resp = &logical.Response{
			Secret: &logical.Secret{},
			Data:   rawData,
		}
	}

	// Check if there is a ttl key
	var ttl string
	ttl, _ = rawData["ttl"].(string)
	if len(ttl) == 0 {
		ttl, _ = rawData["lease"].(string)
	}
	ttlDuration := b.System().DefaultLeaseTTL()
	if len(ttl) != 0 {
		parsedDuration, err := time.ParseDuration(ttl)
		if err != nil {
			resp.AddWarning(fmt.Sprintf("failed to parse stored ttl '%s' for entry; using default", ttl))
		} else {
			ttlDuration = parsedDuration
		}
		if b.generateLeases {
			resp.Secret.Renewable = true
		}
	}

	resp.Secret.TTL = ttlDuration

	return resp, nil
}
Example #2
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
}
Example #3
0
func (b *backend) secretCredsRevoke(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	// Get the username from the internal data
	usernameRaw, ok := req.Secret.InternalData["username"]
	if !ok {
		return nil, fmt.Errorf("secret is missing username internal data")
	}
	username, ok := usernameRaw.(string)

	var revocationSQL string
	var resp *logical.Response

	roleNameRaw, ok := req.Secret.InternalData["role"]
	if ok {
		role, err := b.Role(req.Storage, roleNameRaw.(string))
		if err != nil {
			return nil, err
		}
		if role == nil {
			if resp == nil {
				resp = &logical.Response{}
			}
			resp.AddWarning(fmt.Sprintf("Role %q cannot be found. Using default revocation SQL.", roleNameRaw.(string)))
		} else {
			revocationSQL = role.RevocationSQL
		}
	}

	// Get our connection
	db, err := b.DB(req.Storage)
	if err != nil {
		return nil, err
	}

	switch revocationSQL {

	// This is the default revocation logic. If revocation SQL is provided it
	// is simply executed as-is.
	case "":
		// Check if the role exists
		var exists bool
		err = db.QueryRow("SELECT exists (SELECT rolname FROM pg_roles WHERE rolname=$1);", username).Scan(&exists)
		if err != nil && err != sql.ErrNoRows {
			return nil, err
		}

		if exists == false {
			return resp, nil
		}

		// Query for permissions; we need to revoke permissions before we can drop
		// the role
		// This isn't done in a transaction because even if we fail along the way,
		// we want to remove as much access as possible
		stmt, err := db.Prepare("SELECT DISTINCT table_schema FROM information_schema.role_column_grants WHERE grantee=$1;")
		if err != nil {
			return nil, err
		}
		defer stmt.Close()

		rows, err := stmt.Query(username)
		if err != nil {
			return nil, err
		}
		defer rows.Close()

		const initialNumRevocations = 16
		revocationStmts := make([]string, 0, initialNumRevocations)
		for rows.Next() {
			var schema string
			err = rows.Scan(&schema)
			if err != nil {
				// keep going; remove as many permissions as possible right now
				continue
			}
			revocationStmts = append(revocationStmts, fmt.Sprintf(
				`REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %s FROM %s;`,
				pq.QuoteIdentifier(schema),
				pq.QuoteIdentifier(username)))

			revocationStmts = append(revocationStmts, fmt.Sprintf(
				`REVOKE USAGE ON SCHEMA %s FROM %s;`,
				pq.QuoteIdentifier(schema),
				pq.QuoteIdentifier(username)))
		}

		// for good measure, revoke all privileges and usage on schema public
		revocationStmts = append(revocationStmts, fmt.Sprintf(
			`REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM %s;`,
			pq.QuoteIdentifier(username)))

		revocationStmts = append(revocationStmts, fmt.Sprintf(
			"REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM %s;",
			pq.QuoteIdentifier(username)))

		revocationStmts = append(revocationStmts, fmt.Sprintf(
			"REVOKE USAGE ON SCHEMA public FROM %s;",
			pq.QuoteIdentifier(username)))

		// get the current database name so we can issue a REVOKE CONNECT for
		// this username
		var dbname sql.NullString
		if err := db.QueryRow("SELECT current_database();").Scan(&dbname); err != nil {
			return nil, err
		}

		if dbname.Valid {
			revocationStmts = append(revocationStmts, fmt.Sprintf(
				`REVOKE CONNECT ON DATABASE %s FROM %s;`,
				pq.QuoteIdentifier(dbname.String),
				pq.QuoteIdentifier(username)))
		}

		// again, here, we do not stop on error, as we want to remove as
		// many permissions as possible right now
		var lastStmtError error
		for _, query := range revocationStmts {
			stmt, err := db.Prepare(query)
			if err != nil {
				lastStmtError = err
				continue
			}
			defer stmt.Close()
			_, err = stmt.Exec()
			if err != nil {
				lastStmtError = err
			}
		}

		// can't drop if not all privileges are revoked
		if rows.Err() != nil {
			return nil, fmt.Errorf("could not generate revocation statements for all rows: %s", rows.Err())
		}
		if lastStmtError != nil {
			return nil, fmt.Errorf("could not perform all revocation statements: %s", lastStmtError)
		}

		// Drop this user
		stmt, err = db.Prepare(fmt.Sprintf(
			`DROP ROLE IF EXISTS %s;`, pq.QuoteIdentifier(username)))
		if err != nil {
			return nil, err
		}
		defer stmt.Close()
		if _, err := stmt.Exec(); err != nil {
			return nil, err
		}

	// We have revocation SQL, execute directly, within a transaction
	default:
		tx, err := db.Begin()
		if err != nil {
			return nil, err
		}
		defer func() {
			tx.Rollback()
		}()

		for _, query := range strutil.ParseArbitraryStringSlice(revocationSQL, ";") {
			query = strings.TrimSpace(query)
			if len(query) == 0 {
				continue
			}

			stmt, err := tx.Prepare(Query(query, map[string]string{
				"name": username,
			}))
			if err != nil {
				return nil, err
			}
			defer stmt.Close()

			if _, err := stmt.Exec(); err != nil {
				return nil, err
			}
		}

		if err := tx.Commit(); err != nil {
			return nil, err
		}
	}

	return resp, nil
}
Example #4
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
}
Example #5
0
func (b *backend) secretCredsRevoke(
	req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
	var resp *logical.Response

	// Get the username from the internal data
	usernameRaw, ok := req.Secret.InternalData["username"]
	if !ok {
		return nil, fmt.Errorf("secret is missing username internal data")
	}
	username, ok := usernameRaw.(string)

	// Get our connection
	db, err := b.DB(req.Storage)
	if err != nil {
		return nil, err
	}

	roleName := ""
	roleNameRaw, ok := req.Secret.InternalData["role"]
	if ok {
		roleName = roleNameRaw.(string)
	}

	var role *roleEntry
	if roleName != "" {
		role, err = b.Role(req.Storage, roleName)
		if err != nil {
			return nil, err
		}
	}

	// Use a default SQL statement for revocation if one cannot be fetched from the role
	revocationSQL := defaultRevocationSQL

	if role != nil && role.RevocationSQL != "" {
		revocationSQL = role.RevocationSQL
	} else {
		if resp == nil {
			resp = &logical.Response{}
		}
		resp.AddWarning(fmt.Sprintf("Role %q cannot be found. Using default SQL for revoking user.", roleName))
	}

	// Start a transaction
	tx, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	for _, query := range strutil.ParseArbitraryStringSlice(revocationSQL, ";") {
		query = strings.TrimSpace(query)
		if len(query) == 0 {
			continue
		}

		// This is not a prepared statement because not all commands are supported
		// 1295: This command is not supported in the prepared statement protocol yet
		// Reference https://mariadb.com/kb/en/mariadb/prepare-statement/
		query = strings.Replace(query, "{{name}}", username, -1)
		_, err = tx.Exec(query)
		if err != nil {
			return nil, err
		}

	}

	// Commit the transaction
	if err := tx.Commit(); err != nil {
		return nil, err
	}

	return resp, nil
}
Example #6
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
}