-
Notifications
You must be signed in to change notification settings - Fork 338
Modify DAOS agent to enable generic authentication #16788
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: master
Are you sure you want to change the base?
Conversation
|
A specification we wrote about DAOS Agent authentication. |
|
Errors are component not formatted correctly,Ticket number prefix incorrect,PR title is malformatted. See https://daosio.atlassian.net/wiki/spaces/DC/pages/11133911069/Commit+Comments,Unable to load ticket data |
4813621 to
9644cf0
Compare
tanabarr
left a comment
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.
Looks like a great contribution thank you very much. The changes look good to me, I would like to see some unit testing including some basics on using the access manager. Some documentation on adding new authentication methods and a walk-through of using the access manager example would be good from a usability perspective. I think the original author of the code @kjacque should also take a look to see if I've missed anything.
| } | ||
| validAuthMethods[method] = true | ||
| } | ||
| if len(cfg.credentials.ValidAuthMethods) == 0 { |
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.
So if ValidAuthMethods are specified in the config the default UDS method won't be automatically included and has to be specified within the config? Shouldn't we always provide UDS as a valid method regardless of what extra methods have been added?
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.
So if ValidAuthMethods are specified in the config the default UDS method won't be automatically included and has to be specified within the config?
Yes
Shouldn't we always provide UDS as a valid method regardless of what extra methods have been added?
Personally, I don't think so. If I create my own personal authentication method that I trust, and I don't want to consider "did I configure my Unix groups properly?", it would be nice to disable UDS. I am open to changing my opinion on this, but having more control over valid authentication methods for the agent seems better to me.
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.
OK, that sounds okay as long as it's properly documented and understood.
| var args authArgs | ||
| reqbSize := len(reqb) | ||
| if reqbSize == 0 { | ||
| args.tag = auth.CredentialRequestUnix{}.GetAuthName() |
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.
This seems to imply that UDS should always be available as an authentication option.
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 wanted this code to be backward compatible. "Authentication type" was never specified before this feature because the only option was UDS. By assuming that an empty body means the client wants to use UDS, this feature maintains compatibility with old clients. However, it is possible to disable UDS in the agent through the agent configuration file.
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.
Is there some unit test coverage that covers the cases where 1) empty body results in UDS (this line being executed) and 2) UDS is specified in the config auth types and so args.tag get set as UDS 3) UDS not specified in the config auth types therefore body is not ever empty and another type gets used? Basically it would be good to get decent unit test coverage on this method to ensure all existing authentication types work as expected.
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.
This makes sense. Is security_rpc_test.go the appropriate location to add these unit tests?
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.
Yes it is but I think it might be best to arrange a sync-up with @kjacque before writing tests just in case any design changes need to be made.
| args.tag = auth.CredentialRequestUnix{}.GetAuthName() | ||
| } else if reqbSize >= auth.AuthTagSize { | ||
| copy(args.tag[:], reqb) | ||
| args.reqBody = reqb[auth.AuthTagSize:] |
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.
How does the concatenation of the request body work?
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'm not sure I follow the question, likely stemming from my inexperience with Golang. The purpose of this code is to parse the raw message into the authTag, which identifies the authentication method, and the body of the request into a struct.
I took an approach that is similar to memcpy in C, although I suspect there is a more "Golang-style" way of doing this.
If this does not answer your question, I apologize.
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'm happy if it's got good unit test coverage to give us confidence in the implementation. You could probably use Marshal/Unmarshal for this kind of parsing.
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 will look into using Marshal/Unmarshal and add unit tests.
src/control/security/auth/cred_am.go
Outdated
| ) | ||
|
|
||
| type ( | ||
| CredentialRequestAM struct { |
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.
This file seems to be missing comments to describe exported symbols.
| // an instance to the CredentialRequests list. | ||
| // The agent must be configured to allow an authentication method when it is initalized. | ||
| // By default, only Unix authentication is enabled. | ||
| var CredentialRequests = []CredentialRequestFactory{&CredentialRequestUnix{}, &CredentialRequestAM{}} |
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.
Comment "by default, only unix authentication is enabled" doesn't match the code. List also contains CredentialRequestAM.
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.
by default, only unix authentication is enabled
I think this comment is misleading. CredentialRequests is a list containing every implementation of an authentication method.
This method is where the agent determines which authentication is enabled and this is where the "by default, only unix authentication is enabled" portion is relevant.
The reason for including this comment in the first place is to help developers, but I think they should instead refer to my specification for how to add new authentication and I should remove this portion of the comment. Do you agree?
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.
Yes that sounds good, thanks.
| if req.DomainInfo == nil { | ||
| return nil, err | ||
| } | ||
| if err := func() error { |
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.
nit: the style seems to introduce unnecessary nesting, I would prefer to add a defer func() {log.Error(...)} using the err variable defined in the function signature return types (cred *Credential, err error) to provide error-logging for each of the errors returned (which seems to be the purpose of the func() error syntax. Alternatively it would be fine to not supply a logger to the function and instead log the error in the caller of GetSignedCredential().
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.
My design choice with the nesting in this function is related to how unit testing is done here. I'm not sure if your suggestion is compatible with this unit test, or if I should just rewrite the unit test.
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.
Ok, I didn't realise the test was dependent on this paradigm. It's only a style related comment so can leave it as is, thanks.
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 didn't document this strange design choice in my code, which understandably raises eyebrows. I will add a short comment highlighting this for future developers.
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.
Seconding the initial comment. I don't see why you can't define the closure as a named function (either within this function or outside it). You can pass req into it.
| // Using information stored in the agent's security config, the session/socket, request body, and the agent's signing key, | ||
| // initalize the state of the Credential Request so that it can sign a credential in the future. Return an error if | ||
| // a problem occurs during initializiation. | ||
| InitCredentialRequest(log logging.Logger, sec_cfg *security.CredentialConfig, session *drpc.Session, req_body []byte, key crypto.PrivateKey) error |
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.
Do we want to be passing a logger into credential methods, don't we want to just log the error in the caller. This might reduce the chance of logging information that might be useful to an attacker.
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 don't have much experience with Golang logging. How can I refactor this to log the error in the caller - should I initialize a "default logger" in the caller body?
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.
You already have a logger in the only call I can see: https://github.com/daos-stack/daos/pull/16788/files#diff-3aa160ba407c1ca20c5bc486aa45370dd185a0f0f1eb5eddbebd57293fe9e8a8R221 just log the error after the call if you want to (otherwise just return the error which should be sufficient in most cases, we don't tend to log an error and return it, normally just do one or the other and if returning error use errors.Wrap() et al. to add context to the returned error).
| InitCredentialRequest(log logging.Logger, sec_cfg *security.CredentialConfig, session *drpc.Session, req_body []byte, key crypto.PrivateKey) error | ||
| // Contact a source of authenticity (e.g. domain socket, access manager) using the initalized state of the CredentialRequest | ||
| // to construct a Credential object. | ||
| GetSignedCredential(log logging.Logger, ctx context.Context) (*Credential, error) |
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.
As above.
kjacque
left a comment
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.
Thanks for submitting this PR. It's a good start, but I think we should chat in-depth about the overall design. Right now it seems like the new AM authentication method is a bit jumbled up with the AUTH_SYS (unix) method, and changes are a bit less compartmentalized than I'd like. The initial design was intended to make auth types modular, down to the messages passed via dRPC (see src/proto/security/auth.proto). We also need to be very careful about how tokens and credentials are passed around and who they are accessible to.
If you have any public (or private, I'm at HPE too) specs for the sample access manager, I'd be happy to help with the high level design of this addition. We definitely need to ensure appropriate testing for all the new logic, too.
src/include/daos/security.h
Outdated
| /** Constant that represents the name of the Unix authentication method. */ | ||
| #define AGENT_AUTH_METHOD_AM "acma" |
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.
Looks like the comment wasn't updated. AM/acma == access manager? The name is a bit vague, but if that's what the project is called, no problem with the name.
| int rc; | ||
| uint8_t *token_buffer = NULL; | ||
| unsigned int token_buffer_length = 0; | ||
| char *delegation_token_path = getenv("TOKEN_PATH"); |
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.
This is something I'd expect the agent to know about (maybe part of the agent config?) and return to us in the credential payload.
Without knowing more about the Access Manager protocol, it is tricky to offer specific advice on how we should handle this and where. The agent is the secure component on the client side though. From the libdaos perspective, the agent hands us an appropriate credential, and we blindly send it along to the daos_engine. The daos_engine verifies the cred with the daos_server and stashes it.
src/security/cli_security.c
Outdated
| uint8_t *token_buffer = NULL; | ||
| unsigned int token_buffer_length = 0; | ||
| char *delegation_token_path = getenv("TOKEN_PATH"); | ||
| int use_access_manager = delegation_token_path != NULL && strcmp(delegation_token_path, ""); |
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.
The agent should return a structure based on the supported auth type. I don't think libdaos should care about details like this.
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.
The client should ping the agent to learn what authentication method(s) it should use. If multiple are provided, it will default to the first option.
Separate AUTH_SYS vs AUTH_ACCMAN into different functions.
Switch statement for flavor, make it easy to add new authentication.
| return errors.Wrap(err, "token verification Failed") | ||
| } | ||
|
|
||
| func sysNameToPrincipalName(name string) string { |
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.
This file was intended to encapsulate the AUTH_SYS authentication token type (i.e. using unix usernames/groups). Rather than breaking this up, I think you should consider the flow for this new authentication type separately. If you're free to share any details on the protocol for the access manager, I'd be happy to collaborate.
|
|
||
| var _ cache.ExpirableItem = (*cachedCredential)(nil) | ||
|
|
||
| func getValidAuthMethods(cfg *securityConfig) (AuthValidSet, error) { |
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.
There is a dangling question here: Why would a single DAOS system want to depend on multiple auth types? Unless there's a compelling use case for allowing this, it seems unnecessarily complex, and maybe easier for a user to mess up. I think the auth type needs to be a single type defined in the server config, as a central source. The agent can get this information from the servers.
| func (m *SecurityModule) getCredential(ctx context.Context, session *drpc.Session) ([]byte, error) { | ||
| if session == nil { | ||
| return nil, drpc.NewFailureWithMessage("session is nil") | ||
| func (m *SecurityModule) HandleCall(ctx context.Context, session *drpc.Session, method drpc.Method, reqb []byte) ([]byte, error) { |
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.
The additions in this function seem like they really belong in the GetCredential logic, rather than in the overall SecurityModule.
| CredentialRequestGetSignedTesting := func(ctx context.Context, log logging.Logger, req auth.CredentialRequest) (*auth.Credential, error) { | ||
| r, ok := req.(*auth.CredentialRequestUnix) | ||
| if !ok { | ||
| return nil, errors.New("Testing fn used with non-unix credential request") | ||
| } | ||
| }() | ||
| testing_fn := func() auth.GetSignedCredentialInternalFn { | ||
| var idx int | ||
| return func(_ context.Context, _ *auth.CredentialRequestUnix) (*auth.Credential, error) { | ||
| defer func() { | ||
| if idx < len(tc.responses)-1 { | ||
| idx++ | ||
| } | ||
| }() | ||
| t.Logf("returning response %d: %+v", idx, tc.responses[idx]) | ||
| return tc.responses[idx].cred, tc.responses[idx].err | ||
| } | ||
| }() | ||
| r.GetSignedCredentialInternal = testing_fn | ||
| return r.GetSignedCredential(log, ctx) | ||
| } |
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.
This feels a bit over-complex for a mock function. It seems like testing_fn is what you're really trying to inject here. Worth re-thinking the design approach here I think. Best to design the code to be easy to test.
| if req.DomainInfo == nil { | ||
| return nil, err | ||
| } | ||
| if err := func() error { |
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.
Seconding the initial comment. I don't see why you can't define the closure as a named function (either within this function or outside it). You can pass req into it.
ee2bcc3 to
18a1855
Compare
| bool replace = 13; // Rank's engine instance metadata to be replaced | ||
| } | ||
|
|
||
| message JoinResp { |
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.
Revert changes to this file - all whitespace.
src/proto/security/auth.proto
Outdated
| string origin = 3; // the agent that created this credential | ||
| } | ||
|
|
||
| message AuthArgs |
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.
This is GetCredReq.
src/proto/security/auth.proto
Outdated
| AUTH_NONE = 0; | ||
| AUTH_SYS = 1; | ||
| AUTH_SYS = 1; | ||
| AUTH_AM = 2; |
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.
AM is vague; be more explicit (e.g. AccMan). Add comments along with the proto, it gets exported into generated code.
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.
Add detail for AUTH_SYS/AUTH_UNIX as well.
| "net" | ||
| "os/user" | ||
| <<<<<<< HEAD | ||
| ======= |
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.
Fix merge issue!
| thisscriptname="$(basename "$0")" | ||
| thisscriptpath="$(dirname "$(readlink -f "$0")")" | ||
|
|
||
| echo "start of script: $thisscriptpath/$thisscriptname" |
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.
Revert this change.
src/control/server/mgmt_svc.go
Outdated
| } | ||
|
|
||
| func newMgmtSvc(h *EngineHarness, m *system.Membership, s *raft.Database, c control.UnaryInvoker, p *events.PubSub) *mgmtSvc { | ||
| func newMgmtSvc(h *EngineHarness, m *system.Membership, s *raft.Database, c control.UnaryInvoker, p *events.PubSub, v []uint32) *mgmtSvc { |
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.
Change v to a (authentication!)
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.
Look into importing auth in the server, and then making this an auth.Flavor type.
| // versions: | ||
| // protoc-gen-go v1.34.1 | ||
| // protoc v3.21.12 | ||
| // source: security/auth.proto |
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.
Remove this, added accidentally.
| groupNames, err := r.getGroupNames(r) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to get group names") | ||
| func ParseValidAuthFlavors(authStrings []string) ([]uint32, error) { |
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.
Rename file to just "auth.go", auth_sys is one flavor of auth not the entire thing anymore!
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.
Make this parsing case-insensitive.
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.
Another option: let them start their string with "auth" if they want.
strings.StartsWith("AUTH_") -> just pass the raw string in. Still handle case sensitivity for them.
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.
Also, update the example config to show how to specify auth type.
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.
For public methods, include a documentation comment explaining what the method does!
| return r.getGroup(strconv.Itoa(int(r.DomainInfo.Gid()))) | ||
| func CredentialRequestGetSigned(ctx context.Context, log logging.Logger, req CredentialRequest) (*Credential, error) { | ||
| return req.GetSignedCredential(log, ctx) | ||
| } |
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.
Probably move this to the agent since this is a workaround for an agent-specific issue.
src/control/server/security_rpc.go
Outdated
|
|
||
| // Check our verifier | ||
| err = auth.VerifyToken(key, cred.GetToken(), cred.GetVerifier().GetData()) | ||
| err = auth.VerifyToken(key, cred.GetToken(), cred.GetVerifier().GetData(), m.validAuthFlavors) |
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.
Could remove the parameter from VerifyToken and do the check of "is this flavor valid" in the body of this function.
| "github.com/daos-stack/daos/src/control/lib/hardware/defaults/topology" | ||
| "github.com/daos-stack/daos/src/control/logging" | ||
| "github.com/daos-stack/daos/src/control/security" | ||
| "github.com/daos-stack/daos/src/control/security/auth" |
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.
You're including auth here, so why can't you use auth.Flavor?!
src/control/server/server.go
Outdated
|
|
||
| harness := NewEngineHarness(log).WithFaultDomain(faultDomain) | ||
|
|
||
| validAuthFlavors, err := auth.ParseValidAuthFlavors(cfg.TransportConfig.AuthenticationConfig.ValidAuth) |
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.
Parse this once, find a way to pass this to the security service AND the management service.
src/include/daos/security.h
Outdated
| #include <daos_prop.h> | ||
|
|
||
| /** Constant that represents the upper bound size of a string encoded delegation credential token for the access manager. */ | ||
| #define MAX_DELEGATION_TOKEN_SIZE 10000 |
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.
Move to the function that the client uses to package an "access manager" request to the agent. It is not used anywhere else.
| CacheExpiration time.Duration `yaml:"cache_expiration,omitempty"` | ||
| ClientUserMap ClientUserMap `yaml:"client_user_map,omitempty"` | ||
| ValidAuthMethods []string `yaml:"valid_auth_methods,omitempty"` | ||
| AMConfig AccessManagerConfig `yaml:"access_manager_config,omitempty"` |
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 should not be in the agent!
src/control/security/config.go
Outdated
| AllowInsecure bool `yaml:"allow_insecure"` | ||
| CertificateConfig `yaml:",inline"` | ||
| // authentication | ||
| AuthenticationConfig *AuthenticationConfig `yaml:"auth_config"` |
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.
This should be directly part of the server config file!
src/control/security/config.go
Outdated
|
|
||
| func DefaultAuthenticationConfig() *AuthenticationConfig { | ||
| return &AuthenticationConfig{ | ||
| ValidAuth: []string{"SYS"}, |
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.
Use the actual default string "AUTH_SYS" generated by the protobuf.
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.
And move to the server file.
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.
rename this to auth_accman.go, and the other to auth_sys.go. The current auth_sys.go is now the "generic auth" file so that should just be auth.go.
| getGroupNamesFn func(*CredentialRequestUnix) ([]string, error) | ||
|
|
||
| // CredentialRequest defines the request parameters for GetSignedCredential. | ||
| CredentialRequestUnix struct { |
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.
Rename: AuthSysCredentialRequest. (Obviously change the acc man one too)
| // Using information stored in the agent's security config, the session/socket, request body, and the agent's signing key, | ||
| // initalize the state of the Credential Request so that it can sign a credential in the future. Return an error if | ||
| // a problem occurs during initializiation. | ||
| InitCredentialRequest(log logging.Logger, sec_cfg *security.CredentialConfig, session *drpc.Session, req_body []byte, key crypto.PrivateKey) error |
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.
Rename this to just "init"
|
|
||
| CredentialRequestFactory interface { | ||
| // Allocate, and return, an instance of the type that implements CredentialRequest interface. | ||
| AllocCredentialRequest() CredentialRequest |
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.
See if factory can do the initialization! Seems redundant to do alloc then init.
| CredentialRequestFactory interface { | ||
| // Allocate, and return, an instance of the type that implements CredentialRequest interface. | ||
| AllocCredentialRequest() CredentialRequest | ||
| // Returns a unique 4 character identifier (AuthTag) that refers to the CredentialRequest implementation. |
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.
This comment is outdated, update.
| // Returns a key, as a string, representing a unique identifer specific to the request. This key is used by the cache | ||
| // to remember credentials. It is vital for security that this identifer cannot be forged or easily guessed by the client - | ||
| // otherwise cached credentials can be "stolen". | ||
| CredReqKey() string |
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.
Rename to "GetKey"
| } | ||
| return sysToken, nil | ||
| } | ||
| var CredentialRequests = []CredentialRequestFactory{&CredentialRequestUnix{}, &CredentialRequestAM{}} |
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.
Revisit why I implemented it this way. Can this be simplified? Maybe just a function with a mapping/switch statement between flavors and factories.
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.
Split the factory and the request into two separate things. The requests should not double as factories; the factories should exist only to generate requests!
| } | ||
|
|
||
| // Craft AuthToken | ||
| sys := Sys{ |
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.
Add parsing on the server side to determine what to do with non-Sys tokens.
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.
Add new token for AccMan!
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.
In token definition, call things by their name in the AM (i.e. "roles" not "groups")
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.
My conjecture is that every auth flavor will implement a very similar (if not identical) token structure. I propose that there ought to be a single generic token, and each auth flavor is responsible for implementing GetSignedCredential in a way that conforms to the generic token. I am holding off on making this change until we agree on this design choice.
src/control/security/auth/cred_am.go
Outdated
| return nil, err | ||
| } | ||
|
|
||
| machine_name, identity, err := seperate(authInfo.Identity) |
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.
Use camel case for names.
src/control/lib/control/network.go
Outdated
| ClientNetHint ClientNetworkHint `json:"client_net_hint"` | ||
| AlternateClientNetHints []ClientNetworkHint `json:"secondary_client_net_hints"` | ||
| BuildInfo BuildInfo `json:"build_info"` | ||
| ValidAuthFlavors []uint32 `json:"valid_auth_flavors"` |
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.
Try to change to auth.Flavor
| signCredential credSignerFn | ||
| credCache *credentialCache | ||
| config *securityConfig | ||
| validAuthFlavors []auth.Flavor |
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.
This is in the info cache... why am I reading it here?
| } | ||
|
|
||
| config *securityConfig | ||
| authArgs struct { |
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.
This should be removed.
|
|
||
| var _ cache.ExpirableItem = (*cachedCredential)(nil) | ||
|
|
||
| func getAuthArgs(reqb []byte) (*auth.AuthArgs, error) { |
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.
The caller should pass the flavor I am using, I can change the client logic to ping the agent and ask which flavor to use.
|
|
||
| // NewSecurityModule creates a new module with the given initialized TransportConfig. | ||
| func NewSecurityModule(log logging.Logger, cfg *securityConfig) *SecurityModule { | ||
| func NewSecurityModule(log logging.Logger, cfg *securityConfig) (*SecurityModule, error) { |
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.
This can no longer return errors.
|
|
||
| return m.getCredential(ctx, session) | ||
| } | ||
| m.validAuthFlavors = make([]auth.Flavor, len(resp.ValidAuthFlavors)) |
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.
Should just look in the cache instead of using a second member variable.
| // getCredentials generates a signed user credential based on the authentication method requested. | ||
| func (m *SecurityModule) getCredential(ctx context.Context, session *drpc.Session, args *auth.AuthArgs, req auth.CredentialRequest) ([]byte, error) { | ||
| if len(m.validAuthFlavors) == 0 { | ||
| err := m.retrieveAuthFromServer(ctx) |
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.
Just ask the cache!
| }(); err != nil { | ||
| m.log.Errorf("%s: failed to get user credential: %s", info, err) | ||
| return m.credRespWithStatus(daos.MiscError) | ||
| if errors.Is(err, daos.MiscError) { |
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.
Add new DAOS error code for this specific situation.
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.
include/daos_error.h
and
src/control/lib/daos/
and also documentation where needed.
| } | ||
|
|
||
| func (m *SecurityModule) getValidAuthFlavors(ctx context.Context) ([]byte, error) { | ||
| if len(m.validAuthFlavors) == 0 { |
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.
Get it from the cache!
| numaGetter hardware.ProcessNUMAProvider | ||
| providerIdx uint | ||
|
|
||
| validAuthFlavors []uint32 |
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.
Invesigate if this is necesary!
1aa6045 to
dbf9d36
Compare
| BuildInfo build_info = 9; // Structured server build information | ||
| repeated FabricInterfaces numa_fabric_interfaces = | ||
| 10; // Usable fabric interfaces by NUMA node (populated by agent) | ||
| repeated uint32 valid_auth_flavors = 11; // Authentication flavors allowed by the server |
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 spent hours trying to get this to be an auth.Flavor. I cannot for the life of me figure out the SConscript build system. I defer to the maintainers - it would be unproductive for me to try any longer to make this change myself.
Signed-off-by: JonahRosenblumHPE <[email protected]>
dbf9d36 to
d4ffc6e
Compare
The DAOS agent currently authenticates users using Unix Domain Sockets (UDS). While this works for many users, there is a growing demand for alternative authentication methods. This pull request reworks the DAOS agent code to make it simple to implement new sources of authentication for use in addition to/besides UDS. We also added a new example alternative authentication method in use at our company (part of a project soon to be open-sourced), and are happy to leave it in the codebase as an example.
Steps for the author:
After all prior steps are complete: