mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 10:12:35 +00:00 
			
		
		
		
	 1a01f2a668
			
		
	
	1a01f2a668
	
	
	
		
			
			* Fix semgrep 0.86.5 parsing failures - semgrep https://github.com/returntocorp/semgrep/pull/4671 seems to have introduce this parsing failure within version 0.86.0 and higher - Workaround parsing failure by breaking out the if error check. * Pin semgrep version to 0.86.5 * Fix formatting issues
		
			
				
	
	
		
			227 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			227 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/v2"
 | |
| )
 | |
| 
 | |
| 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:     []string{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 {
 | |
| 		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
 | |
| 		}()
 | |
| 		if 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 {
 | |
| 			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
 | |
| 			}()
 | |
| 			if 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.
 | |
| `
 |