Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vault 5917 allow patch operations to pki roles issuers #15510

Merged
156 changes: 156 additions & 0 deletions builtin/logical/pki/path_fetch_issuers.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ and always set.`,
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
logical.PatchOperation: &framework.PathOperation{
Callback: b.pathPatchIssuer,
// Read more about why these flags are set in backend.go.
ForwardPerformanceStandby: true,
ForwardPerformanceSecondary: true,
},
},

HelpSynopsis: pathGetIssuerHelpSyn,
Expand Down Expand Up @@ -319,6 +325,156 @@ func (b *backend) pathUpdateIssuer(ctx context.Context, req *logical.Request, da
return response, err
}

func (b *backend) pathPatchIssuer(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// Since we're planning on updating issuers here, grab the lock so we've
// got a consistent view.
b.issuersLock.Lock()
defer b.issuersLock.Unlock()

if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not patch issuer until migration has completed"), nil
}

// First we fetch the issuer
issuerName := getIssuerRef(data)
if len(issuerName) == 0 {
return logical.ErrorResponse("missing issuer reference"), nil
}

ref, err := resolveIssuerReference(ctx, req.Storage, issuerName)
if err != nil {
return nil, err
}
if ref == "" {
return logical.ErrorResponse("unable to resolve issuer id for reference: " + issuerName), nil
}

issuer, err := fetchIssuerById(ctx, req.Storage, ref)
if err != nil {
return nil, err
}

// Now We are Looking at What (Might) Have Changed
modified := false

// Name Changes First
_, ok := data.GetOk("issuer_name") // Don't check for conflicts if we aren't updating the name
var oldName string
var newName string
if ok {
newName, err = getIssuerName(ctx, req.Storage, data)
if err != nil && err != errIssuerNameInUse {
// If the error is name already in use, and the new name is the
// old name for this issuer, we're not actually updating the
// issuer name (or causing a conflict) -- so don't err out. Other
// errs should still be surfaced, however.
return logical.ErrorResponse(err.Error()), nil
}
if err == errIssuerNameInUse && issuer.Name != newName {
// When the new name is in use but isn't this name, throw an error.
return logical.ErrorResponse(err.Error()), nil
}
if newName != issuer.Name {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify if we are writing a new name, if non-zero, that it matches nameMatcher.MatchString(newName) so that we have valid names to be use within URLs.

I'm okay with deferring this to another PR as we are missing it here and within the pathUpdateIssuer handler.

oldName = issuer.Name
issuer.Name = newName
modified = true
}
}

// Leaf Not After Changes
rawLeafBehaviorData, ok := data.GetOk("leaf_not_after_behaivor")
if ok {
rawLeafBehavior := rawLeafBehaviorData.(string)
var newLeafBehavior certutil.NotAfterBehavior
switch rawLeafBehavior {
case "err":
newLeafBehavior = certutil.ErrNotAfterBehavior
case "truncate":
newLeafBehavior = certutil.TruncateNotAfterBehavior
case "permit":
newLeafBehavior = certutil.PermitNotAfterBehavior
default:
return logical.ErrorResponse("Unknown value for field `leaf_not_after_behavior`. Possible values are `err`, `truncate`, and `permit`."), nil
}
if newLeafBehavior != issuer.LeafNotAfterBehavior {
issuer.LeafNotAfterBehavior = newLeafBehavior
modified = true
}
}

// Usage Changes
rawUsageData, ok := data.GetOk("usage")
if ok {
rawUsage := rawUsageData.([]string)
newUsage, err := NewIssuerUsageFromNames(rawUsage)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("Unable to parse specified usages: %v - valid values are %v", rawUsage, AllIssuerUsages.Names())), nil
}
if newUsage != issuer.Usage {
issuer.Usage = newUsage
modified = true
}
}

// Manual Chain Changes
newPathData, ok := data.GetOk("manual_chain")
if ok {
newPath := newPathData.([]string)
var updateChain bool
var constructedChain []issuerID
for index, newPathRef := range newPath {
// Allow self for the first entry.
if index == 0 && newPathRef == "self" {
newPathRef = string(ref)
}

resolvedId, err := resolveIssuerReference(ctx, req.Storage, newPathRef)
if err != nil {
return nil, err
}

if index == 0 && resolvedId != ref {
return logical.ErrorResponse(fmt.Sprintf("expected first cert in chain to be a self-reference, but was: %v/%v", newPathRef, resolvedId)), nil
}

constructedChain = append(constructedChain, resolvedId)
if len(issuer.ManualChain) < len(constructedChain) || constructedChain[index] != issuer.ManualChain[index] {
updateChain = true
}
}

if len(issuer.ManualChain) != len(constructedChain) {
updateChain = true
}

if updateChain {
issuer.ManualChain = constructedChain

// Building the chain will write the issuer to disk; no need to do it
// twice.
modified = false
err := rebuildIssuersChains(ctx, req.Storage, issuer)
if err != nil {
return nil, err
}
}
}

if modified {
err := writeIssuer(ctx, req.Storage, issuer)
if err != nil {
return nil, err
}
}

response, err := respondReadIssuer(issuer)
if newName != oldName {
addWarningOnDereferencing(oldName, response, ctx, req.Storage)
}

return response, err
}

func (b *backend) pathGetRawIssuer(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
if b.useLegacyBundleCaStorage() {
return logical.ErrorResponse("Can not get issuer until migration has completed"), nil
Expand Down
Loading