mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-04 04:28:08 +00:00 
			
		
		
		
	* add username customization for rabbitmq * add changelog for rabbitmq * Update builtin/logical/rabbitmq/path_config_connection.go Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com> * updating API docs * moved to changelog folder Co-authored-by: Tom Proctor <tomhjp@users.noreply.github.com>
		
			
				
	
	
		
			225 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			225 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package rabbitmq
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
 | 
						|
	"github.com/hashicorp/vault/sdk/framework"
 | 
						|
	"github.com/hashicorp/vault/sdk/helper/template"
 | 
						|
	"github.com/hashicorp/vault/sdk/logical"
 | 
						|
	rabbithole "github.com/michaelklishin/rabbit-hole"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	defaultUserNameTemplate = `{{ printf "%s-%s" (.DisplayName) (uuid) }}`
 | 
						|
)
 | 
						|
 | 
						|
func pathCreds(b *backend) *framework.Path {
 | 
						|
	return &framework.Path{
 | 
						|
		Pattern: "creds/" + framework.GenericNameRegex("name"),
 | 
						|
		Fields: map[string]*framework.FieldSchema{
 | 
						|
			"name": {
 | 
						|
				Type:        framework.TypeString,
 | 
						|
				Description: "Name of the role.",
 | 
						|
			},
 | 
						|
		},
 | 
						|
 | 
						|
		Callbacks: map[logical.Operation]framework.OperationFunc{
 | 
						|
			logical.ReadOperation: b.pathCredsRead,
 | 
						|
		},
 | 
						|
 | 
						|
		HelpSynopsis:    pathRoleCreateReadHelpSyn,
 | 
						|
		HelpDescription: pathRoleCreateReadHelpDesc,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Issues the credential based on the role name
 | 
						|
func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
 | 
						|
	name := d.Get("name").(string)
 | 
						|
	if name == "" {
 | 
						|
		return logical.ErrorResponse("missing name"), nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Get the role
 | 
						|
	role, err := b.Role(ctx, req.Storage, name)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if role == nil {
 | 
						|
		return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", name)), nil
 | 
						|
	}
 | 
						|
 | 
						|
	config, err := readConfig(ctx, req.Storage)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("unable to read configuration: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	usernameTemplate := config.UsernameTemplate
 | 
						|
	if usernameTemplate == "" {
 | 
						|
		usernameTemplate = defaultUserNameTemplate
 | 
						|
	}
 | 
						|
 | 
						|
	up, err := template.NewTemplate(template.Template(usernameTemplate))
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("unable to initialize username template: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	um := UsernameMetadata{
 | 
						|
		DisplayName: req.DisplayName,
 | 
						|
		RoleName:    name,
 | 
						|
	}
 | 
						|
 | 
						|
	username, err := up.Generate(um)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to generate username: %w", err)
 | 
						|
	}
 | 
						|
	fmt.Printf("username: %s\n", username)
 | 
						|
 | 
						|
	password, err := b.generatePassword(ctx, config.PasswordPolicy)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Get the client configuration
 | 
						|
	client, err := b.Client(ctx, req.Storage)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if client == nil {
 | 
						|
		return logical.ErrorResponse("failed to get the client"), nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Register the generated credentials in the backend, with the RabbitMQ server
 | 
						|
	resp, err := client.PutUser(username, rabbithole.UserSettings{
 | 
						|
		Password: password,
 | 
						|
		Tags:     role.Tags,
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("failed to create a new user with the generated credentials")
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		if err := resp.Body.Close(); err != nil {
 | 
						|
			b.Logger().Error(fmt.Sprintf("unable to close response body: %s", err))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	if !isIn200s(resp.StatusCode) {
 | 
						|
		body, _ := ioutil.ReadAll(resp.Body)
 | 
						|
		return nil, fmt.Errorf("error creating user %s - %d: %s", username, resp.StatusCode, body)
 | 
						|
	}
 | 
						|
 | 
						|
	success := false
 | 
						|
	defer func() {
 | 
						|
		if success {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		// Delete the user because it's in an unknown state.
 | 
						|
		resp, err := client.DeleteUser(username)
 | 
						|
		if err != nil {
 | 
						|
			b.Logger().Error(fmt.Sprintf("deleting %s due to permissions being in an unknown state, but failed: %s", username, err))
 | 
						|
		}
 | 
						|
		if !isIn200s(resp.StatusCode) {
 | 
						|
			body, _ := ioutil.ReadAll(resp.Body)
 | 
						|
			b.Logger().Error(fmt.Sprintf("deleting %s due to permissions being in an unknown state, but error deleting: %d: %s", username, resp.StatusCode, body))
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	// If the role had vhost permissions specified, assign those permissions
 | 
						|
	// to the created username for respective vhosts.
 | 
						|
	for vhost, permission := range role.VHosts {
 | 
						|
		if err := func() error {
 | 
						|
			resp, err := client.UpdatePermissionsIn(vhost, username, rabbithole.Permissions{
 | 
						|
				Configure: permission.Configure,
 | 
						|
				Write:     permission.Write,
 | 
						|
				Read:      permission.Read,
 | 
						|
			})
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			defer func() {
 | 
						|
				if err := resp.Body.Close(); err != nil {
 | 
						|
					b.Logger().Error(fmt.Sprintf("unable to close response body: %s", err))
 | 
						|
				}
 | 
						|
			}()
 | 
						|
			if !isIn200s(resp.StatusCode) {
 | 
						|
				body, _ := ioutil.ReadAll(resp.Body)
 | 
						|
				return fmt.Errorf("error updating vhost permissions for %s - %d: %s", vhost, resp.StatusCode, body)
 | 
						|
			}
 | 
						|
			return nil
 | 
						|
		}(); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// If the role had vhost topic permissions specified, assign those permissions
 | 
						|
	// to the created username for respective vhosts and exchange.
 | 
						|
	for vhost, permissions := range role.VHostTopics {
 | 
						|
		for exchange, permission := range permissions {
 | 
						|
			if err := func() error {
 | 
						|
				resp, err := client.UpdateTopicPermissionsIn(vhost, username, rabbithole.TopicPermissions{
 | 
						|
					Exchange: exchange,
 | 
						|
					Write:    permission.Write,
 | 
						|
					Read:     permission.Read,
 | 
						|
				})
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				defer func() {
 | 
						|
					if err := resp.Body.Close(); err != nil {
 | 
						|
						b.Logger().Error(fmt.Sprintf("unable to close response body: %s", err))
 | 
						|
					}
 | 
						|
				}()
 | 
						|
				if !isIn200s(resp.StatusCode) {
 | 
						|
					body, _ := ioutil.ReadAll(resp.Body)
 | 
						|
					return fmt.Errorf("error updating vhost permissions for %s - %d: %s", vhost, resp.StatusCode, body)
 | 
						|
				}
 | 
						|
				return nil
 | 
						|
			}(); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	success = true
 | 
						|
 | 
						|
	// Return the secret
 | 
						|
	response := b.Secret(SecretCredsType).Response(map[string]interface{}{
 | 
						|
		"username": username,
 | 
						|
		"password": password,
 | 
						|
	}, map[string]interface{}{
 | 
						|
		"username": username,
 | 
						|
	})
 | 
						|
 | 
						|
	// Determine if we have a lease
 | 
						|
	lease, err := b.Lease(ctx, req.Storage)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if lease != nil {
 | 
						|
		response.Secret.TTL = lease.TTL
 | 
						|
		response.Secret.MaxTTL = lease.MaxTTL
 | 
						|
	}
 | 
						|
 | 
						|
	return response, nil
 | 
						|
}
 | 
						|
 | 
						|
func isIn200s(respStatus int) bool {
 | 
						|
	return respStatus >= 200 && respStatus < 300
 | 
						|
}
 | 
						|
 | 
						|
// UsernameMetadata is metadata the database plugin can use to generate a username
 | 
						|
type UsernameMetadata struct {
 | 
						|
	DisplayName string
 | 
						|
	RoleName    string
 | 
						|
}
 | 
						|
 | 
						|
const pathRoleCreateReadHelpSyn = `
 | 
						|
Request RabbitMQ credentials for a certain role.
 | 
						|
`
 | 
						|
 | 
						|
const pathRoleCreateReadHelpDesc = `
 | 
						|
This path reads RabbitMQ credentials for a certain role. The
 | 
						|
RabbitMQ credentials will be generated on demand and will be automatically
 | 
						|
revoked when the lease is up.
 | 
						|
`
 |