feat(client): credential storage for endpoint/token/cert paths (#2)#30
Open
khoaminh2957 wants to merge 1 commit into
Open
feat(client): credential storage for endpoint/token/cert paths (#2)#30khoaminh2957 wants to merge 1 commit into
khoaminh2957 wants to merge 1 commit into
Conversation
Introduces a new core/credentials package that persists permify connection material (endpoint, bearer token, mTLS cert path/key) to disk as YAML at $HOME/.permctl-credentials.yaml with 0600 permissions. Reads enforce the permission contract on POSIX systems and refuse to load world/group-readable files. Writes are atomic via temp file + rename so an interrupted save never leaves a half-written file. core/client/grpc.go's New() now loads stored credentials and uses them to build appropriate grpc.DialOptions: mTLS transport credentials when cert material is present, insecure transport otherwise, plus per-RPC bearer token credentials (secure flavor over TLS, non-secure over plaintext) when a token is stored. The endpoint argument still wins; if blank it falls back to the stored endpoint, and an explicit error is returned when neither is set. Backward compatibility: when no credentials file exists, behavior is unchanged from the previous insecure-only client. Three new Cobra commands wire the storage to the CLI: - permctl login --endpoint/--token/--cert-path/--cert-key (merge-update) - permctl logout (deletes profile entry; removes file when last) - permctl whoami (prints stored creds with token masked) These commands override the root PersistentPreRun so they remain usable before `permctl configure` has ever been run. Tests cover round-trip, multi-profile preservation, 0600 file mode, rejection of insecure file permissions, nil-credential guard, profile deletion (last-profile removes file), missing-file delete no-op, env override, and default-profile fallback. No new module dependencies were introduced; uses the already-vendored gopkg.in/yaml.v3, spf13/cobra, and google.golang.org/grpc/credentials. Closes Permify#2 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2
/claim #2
Summary
Implements local credential storage and retrieval for permctl, satisfying the requirements in #2 (storing endpoint, token, cert path, and cert key in the CLI and consuming them from
client.New).What's new
core/credentials/new packageCredentialsstruct:endpoint,token,cert_path,cert_key(YAML-tagged)Load(profile)/Save(profile, *Credentials)/Delete(profile)plus*From(path, ...)variants for explicit paths (used by tests)GetCredentialsPath()resolves$PERMIFY_CREDENTIALS_FILEthen$HOME/.permctl-credentials.yaml(falls back toos.UserHomeDiron Windows)ErrNotFoundsentinel forerrors.Iscallers0600, parent dir with0700; on POSIX,Loadrefuses to read a file that is world/group readable and printschmod 600instructionsCreateTemp+Chmod+Renamein the same directorycore/client/grpc.gomodifiedNew()PERMIFY_PROFILEenv override)grpc.DialOptions based on what's stored: mTLS viagrpccreds.NewClientTLSFromFilewhencert_path+cert_keyare present, otherwise the previousinsecure.NewCredentials()tokenis set (reusing the existingsecureTokenCredentials/nonSecureTokenCredentialstypes already incore/client/utils.go); secure flavor over TLS, non-secure over plaintextendpointargument still wins; falls back to the stored endpoint when blank; returns a clear error when both are emptyclient.Newbehaves exactly as before (insecure transport, no token)cert_path/cert_keyis configuredcore/cli/auth.gonew Cobra commands wired intocli.New()permctl login --endpoint X --token Y --cert-path Z --cert-key K(merge-updates only the fields you pass; supports--profile)permctl logout(removes the active profile; deletes the file when no profiles remain)permctl whoami(prints the stored values; token is masked tofirst4...last4, short tokens become****)PersistentPreRunso they remain usable beforepermctl configurehas ever been runTests
core/credentials/credentials_test.go(13 cases, all passing on Windows; POSIX-only cases are skipped on Windows):ErrNotFoundErrNotFoundSave->Loadround-trip0600afterSave(POSIX)Loadrefuses0644file with descriptive error (POSIX)Save(nil)is rejectedDeleteremoves only the targeted profileDeleteremoves the file when no profiles remainDeleteon a missing file is a no-opGetCredentialsPathhonorsPERMIFY_CREDENTIALS_FILEGetCredentialsPathdefaults to$HOME/.permctl-credentials.yamldefaultDependencies
No new module dependencies. Uses already-vendored
gopkg.in/yaml.v3,github.com/spf13/cobra, andgoogle.golang.org/grpc/credentials(the last was added implicitly via the existinggrpcimport).Notes for the maintainer
internal/client/client.go. The actual path in this repo iscore/client/grpc.go(there is nointernal/tree), so the modification landed there. Happy to rename / restructure if the project plans a move tointernal/.~/.permctlconfig rather than under~/.config/permify/to match the existing convention set bycmd/permctl/permctl.go. Easy to switch to XDG if preferred.permctl whoamimasks the token but prints cert paths verbatim, since cert paths are filesystem locations rather than secrets.permctl logoutremoves credentials from disk but does not touch the~/.permctlconfig file produced bypermctl configure(those are separate concerns).Test plan
go build ./...cleango test ./...passes (1 new package:core/credentials-> ok)go vet ./...no warningsgofmt -l core/credentials core/cli/auth.go core/client/grpc.goemptylogin-> file written with masked output ->whoamishows masked token, full endpoint/cert paths ->logout->whoamireturns the expected "no credentials stored" error and exits non-zeropermctl --helplists the three new commands alongsideconfigure,data,permission,schema,tenantGenerated with Claude Code.