-
Notifications
You must be signed in to change notification settings - Fork 7
feat: use scoped signing keys for users #104
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,9 @@ type AccountClaims struct { | |
| type AccountStatus struct { | ||
| // +optional | ||
| Claims AccountClaims `json:"claims,omitempty"` | ||
| // +listType=set | ||
| // +optional | ||
| SigningKeys []string `json:"signingKeys,omitempty"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: My guess is that this is a |
||
| // +listType=map | ||
| // +listMapKey=type | ||
| // +patchStrategy=merge | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,10 @@ limitations under the License. | |
| package v1alpha1 | ||
|
|
||
| import ( | ||
| "crypto/md5" | ||
| "encoding/hex" | ||
| "fmt" | ||
| "io" | ||
| "reflect" | ||
|
|
||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
|
@@ -30,6 +33,9 @@ import ( | |
| type UserSpec struct { | ||
| // AccountName references the account used to create the user. | ||
| AccountName string `json:"accountName"` | ||
| // UseSigningKey generates a scopping signing key for the user. | ||
| // +Optional | ||
| UseSigningKey bool `json:"useSigningKey,omitempty"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: The NATS naming of this concept is "Scoped Signing Keys" according to the Signing Keys docs. Re-use this wording when referring to the concept, e.g. |
||
| // DisplayName is an optional name for the NATS resource representing the user. May be derived if absent. | ||
| // +optional | ||
| DisplayName string `json:"displayName,omitempty"` | ||
|
|
@@ -96,6 +102,28 @@ func (u *User) GetUserSecretName() string { | |
| return fmt.Sprintf("%s-nats-user-creds", u.GetName()) | ||
| } | ||
|
|
||
| const ( | ||
| SecretNameUserSignTemplate = "%s-u-sign-%s" // #nosec G101 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: This constant is also defined in |
||
| ) | ||
|
|
||
| func mustGenerateShortHashFromID(ID string) string { | ||
| hasher := md5.New() | ||
| _, err := io.WriteString(hasher, ID) | ||
| if err != nil { | ||
| panic(fmt.Sprintf("failed to generate hash from ID: %v", err)) | ||
| } | ||
|
|
||
| hash := hex.EncodeToString(hasher.Sum(nil)) | ||
| if len(hash) > 6 { | ||
| return hash[:6] | ||
| } | ||
| return hash | ||
| } | ||
|
|
||
| func (u *User) GetUserSigningKeySecretName() string { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: The signing key secret name is rather an implementation detail than related to the API model. Suggestion is to move this logic into the manager/domain instead. |
||
| return fmt.Sprintf(SecretNameUserSignTemplate, u.GetName(), mustGenerateShortHashFromID(u.GetName())) | ||
| } | ||
|
|
||
| // +kubebuilder:object:root=true | ||
|
|
||
| // UserList contains a list of User. | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import ( | |
| "log" | ||
| "maps" | ||
| "os" | ||
| "strings" | ||
|
|
||
| "github.com/WirelessCar/nauth/internal/k8s" | ||
| v1 "k8s.io/api/core/v1" | ||
|
|
@@ -48,13 +49,13 @@ func NewClient(client client.Client, opts ...Option) *Client { | |
| if err != nil { | ||
| log.Fatalf("Failed to read namespace: %v", err) | ||
| } | ||
| secretClient.controllerNamespace = string(namespacePath) | ||
| secretClient.controllerNamespace = strings.TrimSpace(string(namespacePath)) | ||
| } | ||
|
|
||
| return secretClient | ||
| } | ||
|
|
||
| func (k *Client) Apply(ctx context.Context, owner *Owner, meta metav1.ObjectMeta, valueMap map[string]string) error { | ||
| func (k *Client) Apply(ctx context.Context, owner *Owner, meta metav1.ObjectMeta, valueMap map[string]string, update bool) error { | ||
| if !isManagedSecret(&meta) { | ||
| return fmt.Errorf("label %s not supplied by secret %s/%s", k8s.LabelManaged, meta.Namespace, meta.Name) | ||
| } | ||
|
|
@@ -77,6 +78,10 @@ func (k *Client) Apply(ctx context.Context, owner *Owner, meta metav1.ObjectMeta | |
| return fmt.Errorf("failed to create secret: %w", err) | ||
| } | ||
| } else { | ||
| if !update { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: If Signing Keys are being used, hence |
||
| // Do not update the secret, we just needed to know it's here | ||
| return nil | ||
| } | ||
| if !isManagedSecret(¤tSecret.ObjectMeta) { | ||
| return fmt.Errorf("existing secret %s/%s not managed by nauth", meta.Namespace, meta.Name) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Correct me if I'm wrong. If a user secret is exposed, and we want to revoke that signing key to deny the credentials by either deleting it, or re-creating it (along with a new user secret). How would the process look like? What resource do I (manually) delete/modify/trigger in Kubernetes to achieve this?