feat: when a vault refuses, what to check: PIM, the owner, or the network
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bf374176df
commit
206e26fdba
3 files changed
+38
-4
No files matched your search
+4
-2
@@ -40,8 +40,10 @@ vault resources (e.g. *Reader*). Key Vault data-plane access is separate
|
|||||||
| read secret values | *Key Vault Secrets User* |
|
| read secret values | *Key Vault Secrets User* |
|
||||||
| read certificate details | *Key Vault Certificate User* |
|
| read certificate details | *Key Vault Certificate User* |
|
||||||
|
|
||||||
A missing permission is shown in the pane, including the action that was
|
A missing permission is shown in the pane, shortened to what was denied
|
||||||
denied.
|
(the action, or the access policy permission), with what to check: a
|
||||||
|
role or group to activate in PIM, access from the vault's owner, or, when
|
||||||
|
its firewall refused, the network you're on.
|
||||||
|
|
||||||
## Vaults you can't discover
|
## Vaults you can't discover
|
||||||
|
|
||||||
|
|||||||
@@ -175,8 +175,11 @@ func TestForbiddenIsShortened(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
if got, want := err.Error(), "403 Forbidden: Caller is not authorized to perform action on resource."; got != want {
|
if got, want := err.Error(), "403 Forbidden: Caller is not authorized to perform action on resource.\nCheck: have you activated your role or group in PIM?"; !strings.HasPrefix(got, want) {
|
||||||
t.Errorf("error = %q, want %q", got, want)
|
t.Errorf("error = %q, want %q…", got, want)
|
||||||
|
}
|
||||||
|
if hint := deniedHint("Client address is not authorized and caller is not a trusted service."); !strings.Contains(hint, "network") {
|
||||||
|
t.Errorf("firewall hint = %q", hint)
|
||||||
}
|
}
|
||||||
var re *azcore.ResponseError
|
var re *azcore.ResponseError
|
||||||
if !errors.As(err, &re) || re.StatusCode != 403 {
|
if !errors.As(err, &re) || re.StatusCode != 403 {
|
||||||
@@ -184,6 +187,13 @@ func TestForbiddenIsShortened(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShortenPolicyDenied(t *testing.T) {
|
||||||
|
msg := "The user, group or application 'appid=04b07795;oid=1234;numgroups=83;iss=https://sts.windows.net/t/' does not have secrets list permission on key vault 'kv-app;location=norwayeast'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287"
|
||||||
|
if got := shortenDenied(msg); got != "no secrets list permission in the vault's access policies" {
|
||||||
|
t.Errorf("shortenDenied = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestShortenDenied(t *testing.T) {
|
func TestShortenDenied(t *testing.T) {
|
||||||
msg := "Caller is not authorized to perform action on resource.\r\nIf role assignments, deny assignments or role definitions were changed recently, please observe propagation time.\r\nCaller: appid=04b07795;oid=1234\r\nAction: 'Microsoft.KeyVault/vaults/secrets/readMetadata/action'\r\nResource: '/subscriptions/x/vaults/kv'"
|
msg := "Caller is not authorized to perform action on resource.\r\nIf role assignments, deny assignments or role definitions were changed recently, please observe propagation time.\r\nCaller: appid=04b07795;oid=1234\r\nAction: 'Microsoft.KeyVault/vaults/secrets/readMetadata/action'\r\nResource: '/subscriptions/x/vaults/kv'"
|
||||||
want := "Caller is not authorized to perform action on resource; missing Microsoft.KeyVault/vaults/secrets/readMetadata/action"
|
want := "Caller is not authorized to perform action on resource; missing Microsoft.KeyVault/vaults/secrets/readMetadata/action"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
||||||
@@ -282,10 +283,17 @@ func cleanError(err error) error {
|
|||||||
return &serviceError{status: re.StatusCode, msg: shortenDenied(msg), err: err}
|
return &serviceError{status: re.StatusCode, msg: shortenDenied(msg), err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var policyDenied = regexp.MustCompile(`does not have (\w+ \w+) permission on key vault`)
|
||||||
|
|
||||||
// shortenDenied reduces Key Vault's RBAC denial, which runs to several
|
// shortenDenied reduces Key Vault's RBAC denial, which runs to several
|
||||||
// sentences plus caller and resource IDs, to its first sentence and the
|
// sentences plus caller and resource IDs, to its first sentence and the
|
||||||
// missing action.
|
// missing action.
|
||||||
func shortenDenied(msg string) string {
|
func shortenDenied(msg string) string {
|
||||||
|
// Access policies: "The user, group or application 'appid=…;oid=…'
|
||||||
|
// does not have secrets list permission on key vault 'kv;location=…'".
|
||||||
|
if m := policyDenied.FindStringSubmatch(msg); m != nil {
|
||||||
|
return "no " + m[1] + " permission in the vault's access policies"
|
||||||
|
}
|
||||||
const marker = "Action: '"
|
const marker = "Action: '"
|
||||||
i := strings.Index(msg, marker)
|
i := strings.Index(msg, marker)
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
@@ -314,7 +322,21 @@ func (e *serviceError) Error() string {
|
|||||||
if e.msg != "" {
|
if e.msg != "" {
|
||||||
s += ": " + e.msg
|
s += ": " + e.msg
|
||||||
}
|
}
|
||||||
|
if e.status == http.StatusForbidden {
|
||||||
|
s += "\n" + deniedHint(e.msg)
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deniedHint is what to check when Key Vault refuses: the network, for
|
||||||
|
// its firewall's refusals, else access. On a line of its own, after the
|
||||||
|
// message.
|
||||||
|
func deniedHint(msg string) string {
|
||||||
|
if strings.Contains(msg, "Client address is not authorized") || strings.Contains(msg, "network access") ||
|
||||||
|
strings.Contains(msg, "private link") {
|
||||||
|
return "Check: are you on a network the vault lets in (VPN, office)? Its firewall limits where it can be reached from."
|
||||||
|
}
|
||||||
|
return "Check: have you activated your role or group in PIM? Has the vault's owner given you access (a Key Vault role, or its access policies)?"
|
||||||
|
}
|
||||||
|
|
||||||
func (e *serviceError) Unwrap() error { return e.err }
|
func (e *serviceError) Unwrap() error { return e.err }
|
||||||
Reference in new issue
Block a user