-
Notifications
You must be signed in to change notification settings - Fork 969
azkv: Allow specifying auth method and add cachable authentication methods #1777
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?
Conversation
Thanks for your contribution! I've been looking at this and thinking about this since yesterday, and I think the general idea is sound. (I'm not an Azure user though... I only use AZP for another open source project, and if you're writing that Azure is awful and slow to work with, I can guess what you mean since AZP definitely also falls into that category :D) I'm a bit hesitant on the caching parts, since that writes credentials to disk (I guess not in an encrypted form, and even if its encrypted, it likely won't be anything safe). This should definitely be explicitly mentioned in the documentation, and maybe it would be better to rename the corresponding values for I also have some smaller comments, I'm adding them here (even though they're not relevant to the general design) before I forget about them:
CC @hiddeco, since you created #1067 which removed the |
(And I think it would be better if Azure's SDK would take care of things like this in its default auth flow, or would offer another auth flow that allows the user to configure this via env variables. But if it doens't offer that, I guess we have to do it ourselves...) |
The AuthenticationRecord that I am storing in the function cacheStoreRecord is not actually a secret, it only contains information like identifiers, version, username, etc. The actual storing of the token is handled by the azure-sdk, the tokens are encrypted at rest via an os specific mechanisms: Persistent caches are encrypted at rest using a mechanism that depends on the operating system see docs
Their respective implementations can be found here:
Since the token is encrypted using a method specfic to the OS it makes it difficult to think of what to add as a contextual information in I'll submit an update in the next few days with the fixes to the comments, adding some docs and tests (if I can figure something out to get around the interactive parts). I just have one question: should I keep the environment variable |
Thanks for clarifying that! In that case, I don't mind the caching on disk.
Since no sensitive information is cached, I think it's OK to not use a prefix, or simply use
I would keep the prefix. I think using env variables without a |
I've updated the minor comments and added docs for the feature. However I am not sure what to do about the If I run
If I run
If I run
|
Please note that you must sign off every single commit, otherwise we cannot merge this (see the failing DCO test). |
5515ec0
to
081c0f1
Compare
Since the Since we've been using the forked version for a little while now I've realized that on the initial authentication the browser and device-code login auth methods would pollute the |
…user authentication methods Signed-off-by: Isak Johansson <[email protected]>
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.
To actually get this into a shape where it is mergable, changes have to be made to build the macOS binaries with CGO enabled. This will likely raise some issues around cross-compilation.
If we go the CGO route, we should probably plan out how this affects our CI, release process, and broader contributor experience.
func sopsCacheDir() (string, error) { | ||
userCacheDir, err := os.UserCacheDir() | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
cacheDir := filepath.Join(userCacheDir, "/sops") | ||
|
||
if err = os.MkdirAll(cacheDir, 0o700); err != nil { | ||
return "", err | ||
} | ||
|
||
return cacheDir, nil | ||
} |
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.
I think it would be better to create a cache
package, with something like:
var (
cacheDir string
cacheDirErr error
cacheDirOnce sync.Once
)
// Directory returns the path to the SOPS cache directory, creating it if it
// does not exist. The directory with the name "sops" is created in the user's
// cache directory (see [os.UserCacheDir]). Once created, it will not be
// recreated, even if the function is called multiple times. If an error occurs
// while creating the directory, it will be returned on later calls.
func Directory() (string, error) {
cacheDirOnce.Do(func() {
base, err := os.UserCacheDir()
if err != nil {
cacheDirErr = err
return
}
cacheDir = filepath.Join(base, "sops")
cacheDirErr = os.MkdirAll(cacheDir, 0o700)
})
if cacheDirErr != nil {
return "", cacheDirErr
}
return cacheDir, nil
}
// Put writes the provided data to a file in the SOPS cache directory.
// At present, no validation is performed on the file name, and it is the
// caller's responsibility to ensure that the file name is safe (i.e., does not
// contain path traversal characters or other unsafe elements).
func Put(fileName string, data []byte) error {
dir, err := Directory()
if err != nil {
return err
}
filePath := filepath.Join(dir, fileName)
if err = os.WriteFile(filePath, data, 0o600); err != nil {
return fmt.Errorf("failed to write to cache file %s: %w", filePath, err)
}
return nil
}
// Get reads the contents of a file from the SOPS cache directory.
// If the file does not exist or cannot be read, an error will be returned.
func Get(fileName string) ([]byte, error) {
dir, err := Directory()
if err != nil {
return nil, err
}
filePath := filepath.Join(dir, fileName)
data, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read cache file %s: %w", filePath, err)
}
return data, nil
}
// Exists checks if a file exists in the SOPS cache directory.
func Exists(fileName string) (bool, error) {
dir, err := Directory()
if err != nil {
return false, err
}
filePath := filepath.Join(dir, fileName)
if _, err = os.Stat(filePath); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
"context" | ||
"encoding/base64" | ||
"fmt" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
"time" | ||
|
||
"encoding/json" | ||
"os" |
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.
These imports aren't properly ordered.
Azure is just really awful and slow to work with, and using sops with a azkv key to edit secrets from e.g. your laptop where you are authenticating with your user and not through a SP/MSI is nightmarishly slow.
To improve the situation this PR implements:
InteractiveBrowserCredential
andDeviceCodeCredential
which adds a very significant speedup compared toAzureCliCredential
. See below.The time to unlock a secret using sops
v3.9.4
and authenticating as a user throughazure-cli
:The time to unlock a secret using my PR and authenticating as a user with
InteractiveBrowserCredential
after the initial authentication:Related issues: #885, #1606
I'm happy to add documentation, tests or whatever is needed. I just don't want to go through the trouble before I know if the design is acceptable, and that there is a chance that this could be merged.