github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/agent/secretsmanager/client.go (about)

     1  // Copyright 2021 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secretsmanager
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names/v5"
     9  
    10  	"github.com/juju/juju/api/base"
    11  	commonsecretbackends "github.com/juju/juju/api/common/secretbackends"
    12  	apiwatcher "github.com/juju/juju/api/watcher"
    13  	apiservererrors "github.com/juju/juju/apiserver/errors"
    14  	coresecrets "github.com/juju/juju/core/secrets"
    15  	"github.com/juju/juju/core/watcher"
    16  	"github.com/juju/juju/rpc/params"
    17  )
    18  
    19  // Client is the api client for the SecretsManager facade.
    20  type Client struct {
    21  	facade base.FacadeCaller
    22  	*commonsecretbackends.Client
    23  }
    24  
    25  // NewClient creates a secrets api client.
    26  func NewClient(caller base.APICaller) *Client {
    27  	facade := base.NewFacadeCaller(caller, "SecretsManager")
    28  	return &Client{
    29  		facade: facade,
    30  		Client: commonsecretbackends.NewClient(facade),
    31  	}
    32  }
    33  
    34  // CreateSecretURIs generates new secret URIs.
    35  func (c *Client) CreateSecretURIs(count int) ([]*coresecrets.URI, error) {
    36  	var results params.StringResults
    37  
    38  	if count <= 0 {
    39  		return nil, errors.NotValidf("secret URi count %d", count)
    40  	}
    41  	if err := c.facade.FacadeCall("CreateSecretURIs", params.CreateSecretURIsArg{
    42  		Count: count,
    43  	}, &results); err != nil {
    44  		return nil, errors.Trace(err)
    45  	}
    46  	if n := len(results.Results); n != count {
    47  		return nil, errors.Errorf("expected %d result(s), got %d", count, n)
    48  	}
    49  	uris := make([]*coresecrets.URI, count)
    50  	for i, s := range results.Results {
    51  		if err := s.Error; err != nil {
    52  			return nil, errors.Trace(err)
    53  		}
    54  		uri, err := coresecrets.ParseURI(s.Result)
    55  		if err != nil {
    56  			return nil, errors.Trace(err)
    57  		}
    58  		uris[i] = uri
    59  	}
    60  	return uris, nil
    61  }
    62  
    63  // WatchConsumedSecretsChanges returns a watcher which serves changes to
    64  // secrets payloads for any secrets consumed by the specified unit.
    65  func (c *Client) WatchConsumedSecretsChanges(unitName string) (watcher.StringsWatcher, error) {
    66  	var results params.StringsWatchResults
    67  	args := params.Entities{
    68  		Entities: []params.Entity{{Tag: names.NewUnitTag(unitName).String()}},
    69  	}
    70  	err := c.facade.FacadeCall("WatchConsumedSecretsChanges", args, &results)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	if len(results.Results) != 1 {
    75  		return nil, errors.Errorf("expected 1 result, got %d", len(results.Results))
    76  	}
    77  	result := results.Results[0]
    78  	if result.Error != nil {
    79  		return nil, apiservererrors.RestoreError(result.Error)
    80  	}
    81  	w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result)
    82  	return w, nil
    83  }
    84  
    85  // WatchObsolete returns a watcher for notifying when:
    86  //   - a secret owned by the entity is deleted
    87  //   - a secret revision owed by the entity no longer
    88  //     has any consumers
    89  //
    90  // Obsolete revisions results are "uri/revno" and deleted
    91  // secret results are "uri".
    92  func (c *Client) WatchObsolete(ownerTags ...names.Tag) (watcher.StringsWatcher, error) {
    93  	var result params.StringsWatchResult
    94  	args := params.Entities{Entities: make([]params.Entity, len(ownerTags))}
    95  	for i, tag := range ownerTags {
    96  		args.Entities[i] = params.Entity{Tag: tag.String()}
    97  	}
    98  	err := c.facade.FacadeCall("WatchObsolete", args, &result)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	if result.Error != nil {
   103  		return nil, apiservererrors.RestoreError(result.Error)
   104  	}
   105  	w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result)
   106  	return w, nil
   107  }
   108  
   109  // GetConsumerSecretsRevisionInfo returns the current revision and labels for secrets consumed
   110  // by the specified unit.
   111  func (c *Client) GetConsumerSecretsRevisionInfo(unitName string, uris []string) (map[string]coresecrets.SecretRevisionInfo, error) {
   112  	var results params.SecretConsumerInfoResults
   113  	args := params.GetSecretConsumerInfoArgs{
   114  		ConsumerTag: names.NewUnitTag(unitName).String(),
   115  		URIs:        uris,
   116  	}
   117  
   118  	err := c.facade.FacadeCall("GetConsumerSecretsRevisionInfo", args, &results)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	if len(results.Results) != len(uris) {
   123  		return nil, errors.Errorf("expected %d result, got %d", len(uris), len(results.Results))
   124  	}
   125  	info := make(map[string]coresecrets.SecretRevisionInfo)
   126  	for i, latest := range results.Results {
   127  		if err := results.Results[i].Error; err != nil {
   128  			// If deleted or now unauthorised, do not report any info for this url.
   129  			if err.Code == params.CodeNotFound || err.Code == params.CodeUnauthorized {
   130  				continue
   131  			}
   132  			return nil, errors.Annotatef(err, "finding latest info for secret %q", uris[i])
   133  		}
   134  		info[uris[i]] = coresecrets.SecretRevisionInfo{
   135  			Revision: latest.Revision,
   136  			Label:    latest.Label,
   137  		}
   138  	}
   139  	return info, err
   140  }
   141  
   142  // SecretMetadata returns metadata for the specified secrets.
   143  func (c *Client) SecretMetadata() ([]coresecrets.SecretOwnerMetadata, error) {
   144  	var results params.ListSecretResults
   145  	err := c.facade.FacadeCall("GetSecretMetadata", nil, &results)
   146  	if err != nil {
   147  		return nil, errors.Trace(err)
   148  	}
   149  	var result []coresecrets.SecretOwnerMetadata
   150  	for _, info := range results.Results {
   151  		uri, err := coresecrets.ParseURI(info.URI)
   152  		if err != nil {
   153  			return nil, errors.NotValidf("secret URI %q", info.URI)
   154  		}
   155  		md := coresecrets.SecretMetadata{
   156  			URI:              uri,
   157  			OwnerTag:         info.OwnerTag,
   158  			Description:      info.Description,
   159  			Label:            info.Label,
   160  			RotatePolicy:     coresecrets.RotatePolicy(info.RotatePolicy),
   161  			LatestRevision:   info.LatestRevision,
   162  			LatestExpireTime: info.LatestExpireTime,
   163  			NextRotateTime:   info.NextRotateTime,
   164  		}
   165  		for _, g := range info.Access {
   166  			md.Access = append(md.Access, coresecrets.AccessInfo{
   167  				Target: g.TargetTag, Scope: g.ScopeTag, Role: g.Role,
   168  			})
   169  		}
   170  		revisions := make([]int, len(info.Revisions))
   171  		for i, r := range info.Revisions {
   172  			revisions[i] = r.Revision
   173  		}
   174  		result = append(result, coresecrets.SecretOwnerMetadata{
   175  			Metadata:  md,
   176  			Revisions: revisions,
   177  		})
   178  	}
   179  	return result, nil
   180  }
   181  
   182  // WatchSecretsRotationChanges returns a watcher which serves changes to
   183  // secrets rotation config for any secrets managed by the specified owner.
   184  func (c *Client) WatchSecretsRotationChanges(ownerTags ...names.Tag) (watcher.SecretTriggerWatcher, error) {
   185  	var result params.SecretTriggerWatchResult
   186  	args := params.Entities{Entities: make([]params.Entity, len(ownerTags))}
   187  	for i, tag := range ownerTags {
   188  		args.Entities[i] = params.Entity{Tag: tag.String()}
   189  	}
   190  	err := c.facade.FacadeCall("WatchSecretsRotationChanges", args, &result)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	if result.Error != nil {
   195  		return nil, result.Error
   196  	}
   197  	w := apiwatcher.NewSecretsTriggerWatcher(c.facade.RawAPICaller(), result)
   198  	return w, nil
   199  }
   200  
   201  // SecretRotated records the outcome of rotating a secret.
   202  func (c *Client) SecretRotated(uri string, oldRevision int) error {
   203  	secretUri, err := coresecrets.ParseURI(uri)
   204  	if err != nil {
   205  		return errors.Trace(err)
   206  	}
   207  
   208  	var results params.ErrorResults
   209  	args := params.SecretRotatedArgs{
   210  		Args: []params.SecretRotatedArg{{
   211  			URI:              secretUri.String(),
   212  			OriginalRevision: oldRevision,
   213  		}},
   214  	}
   215  	err = c.facade.FacadeCall("SecretsRotated", args, &results)
   216  	if err != nil {
   217  		return errors.Trace(err)
   218  	}
   219  	if len(results.Results) != 1 {
   220  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   221  	}
   222  	result := results.Results[0]
   223  	if result.Error != nil {
   224  		return result.Error
   225  	}
   226  	return nil
   227  }
   228  
   229  // WatchSecretRevisionsExpiryChanges returns a watcher which serves changes to
   230  // secret revision expiry config for any secrets managed by the specified owner.
   231  func (c *Client) WatchSecretRevisionsExpiryChanges(ownerTags ...names.Tag) (watcher.SecretTriggerWatcher, error) {
   232  	var result params.SecretTriggerWatchResult
   233  	args := params.Entities{Entities: make([]params.Entity, len(ownerTags))}
   234  	for i, tag := range ownerTags {
   235  		args.Entities[i] = params.Entity{Tag: tag.String()}
   236  	}
   237  	err := c.facade.FacadeCall("WatchSecretRevisionsExpiryChanges", args, &result)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  	if result.Error != nil {
   242  		return nil, result.Error
   243  	}
   244  	w := apiwatcher.NewSecretsTriggerWatcher(c.facade.RawAPICaller(), result)
   245  	return w, nil
   246  }
   247  
   248  // SecretRevokeGrantArgs holds the args used to grant or revoke access to a secret.
   249  // To grant access, specify one of ApplicationName or UnitName, plus optionally RelationId.
   250  // To revoke access, specify one of ApplicationName or UnitName.
   251  type SecretRevokeGrantArgs struct {
   252  	ApplicationName *string
   253  	UnitName        *string
   254  	RelationKey     *string
   255  	Role            coresecrets.SecretRole
   256  }
   257  
   258  // Grant grants access to the specified secret.
   259  func (c *Client) Grant(uri *coresecrets.URI, p *SecretRevokeGrantArgs) error {
   260  	args := grantRevokeArgsToParams(p, uri)
   261  	var results params.ErrorResults
   262  	err := c.facade.FacadeCall("SecretsGrant", args, &results)
   263  	if err != nil {
   264  		return errors.Trace(err)
   265  	}
   266  	if len(results.Results) != 1 {
   267  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   268  	}
   269  	result := results.Results[0]
   270  	if result.Error != nil {
   271  		return result.Error
   272  	}
   273  	return nil
   274  }
   275  
   276  func grantRevokeArgsToParams(p *SecretRevokeGrantArgs, secretUri *coresecrets.URI) params.GrantRevokeSecretArgs {
   277  	var subjectTag, scopeTag string
   278  	if p.ApplicationName != nil {
   279  		subjectTag = names.NewApplicationTag(*p.ApplicationName).String()
   280  	}
   281  	if p.UnitName != nil {
   282  		subjectTag = names.NewUnitTag(*p.UnitName).String()
   283  	}
   284  	if p.RelationKey != nil {
   285  		scopeTag = names.NewRelationTag(*p.RelationKey).String()
   286  	} else {
   287  		scopeTag = subjectTag
   288  	}
   289  	args := params.GrantRevokeSecretArgs{
   290  		Args: []params.GrantRevokeSecretArg{{
   291  			URI:         secretUri.String(),
   292  			ScopeTag:    scopeTag,
   293  			SubjectTags: []string{subjectTag},
   294  			Role:        string(p.Role),
   295  		}},
   296  	}
   297  	return args
   298  }
   299  
   300  // Revoke revokes access to the specified secret.
   301  func (c *Client) Revoke(uri *coresecrets.URI, p *SecretRevokeGrantArgs) error {
   302  	args := grantRevokeArgsToParams(p, uri)
   303  	var results params.ErrorResults
   304  	err := c.facade.FacadeCall("SecretsRevoke", args, &results)
   305  	if err != nil {
   306  		return errors.Trace(err)
   307  	}
   308  	if len(results.Results) != 1 {
   309  		return errors.Errorf("expected 1 result, got %d", len(results.Results))
   310  	}
   311  	result := results.Results[0]
   312  	if result.Error != nil {
   313  		return result.Error
   314  	}
   315  	return nil
   316  }