-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add authentication that supports usernames and passwords
This commit adds `ask` mode authentication that allows node operators to configure Bacalhau to ask the user for arbitrary information to be used as a credential. This method can be used to implement basic usernames and passwords, shared secrets, security questions and even 2FA. The associated policy additionally returns a JSON Schema to show what information is required. The CLI uses the schema to ask the user for the right information. In the future, the Web UI will do this as well.
- Loading branch information
Showing
20 changed files
with
607 additions
and
18 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package auth | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/santhosh-tekuri/jsonschema/v5" | ||
"github.com/spf13/cobra" | ||
"golang.org/x/term" | ||
) | ||
|
||
// Returns a responder that responds to authentication requirements of type | ||
// `authn.MethodTypeAsk`. Reads the JSON Schema returned by the `ask` endpoint | ||
// and uses it to ask appropriate questions to the user on their terminal, and | ||
// then returns their response as serialized JSON. | ||
func askResponder(cmd *cobra.Command) responder { | ||
return func(request *json.RawMessage) ([]byte, error) { | ||
compiler := jsonschema.NewCompiler() | ||
compiler.ExtractAnnotations = true | ||
|
||
if err := compiler.AddResource("", bytes.NewReader(*request)); err != nil { | ||
return nil, err | ||
} | ||
|
||
schema, err := compiler.Compile("") | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
response := make(map[string]any, len(schema.Properties)) | ||
for _, name := range schema.Required { | ||
subschema := schema.Properties[name] | ||
|
||
if len(subschema.Types) < 1 { | ||
return nil, fmt.Errorf("invalid schema: property %q has no type", name) | ||
} | ||
|
||
typ := subschema.Types[0] | ||
if typ == "object" { | ||
return nil, fmt.Errorf("invalid schema: property %q has non-scalar type", name) | ||
} | ||
|
||
fmt.Fprintf(cmd.ErrOrStderr(), "%s: ", name) | ||
|
||
var input []byte | ||
var err error | ||
|
||
// If the property is marked as write only, assume it is a sensitive | ||
// value and make sure we don't display it in the terminal | ||
if subschema.WriteOnly { | ||
input, err = term.ReadPassword(int(os.Stdin.Fd())) | ||
fmt.Fprintln(cmd.ErrOrStderr()) | ||
} else { | ||
reader := bufio.NewScanner(cmd.InOrStdin()) | ||
if reader.Scan() { | ||
input = reader.Bytes() | ||
} | ||
err = reader.Err() | ||
} | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
response[name] = string(input) | ||
} | ||
|
||
respBytes, err := json.Marshal(response) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return respBytes, schema.Validate(response) | ||
} | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package bacalhau.authn | ||
|
||
import rego.v1 | ||
|
||
schema := { | ||
"type": "object", | ||
"properties": {"magic": {"type": "string"}}, | ||
"required": ["magic"], | ||
} | ||
|
||
token := t if { | ||
input.magic == "open sesame" | ||
|
||
t := io.jwt.encode_sign( | ||
{ | ||
"typ": "JWT", | ||
"alg": "RS256", | ||
}, | ||
{ | ||
"iss": input.nodeId, | ||
"sub": "aladdin", | ||
"aud": [input.nodeId], | ||
"iat": now, | ||
"exp": one_month, | ||
"ns": { | ||
# Read-only access to all namespaces | ||
"*": read_only, | ||
# Writable access to own namespace | ||
"genie": full_access, | ||
}, | ||
}, | ||
input.signingKey, | ||
) | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package bacalhau.authn | ||
|
||
import rego.v1 | ||
|
||
# Implements a policy where clients that supply a valid username and password | ||
# are permitted access. Anonymous users are not permitted. | ||
# | ||
# Modify the `userlist` to control what users are permitted access. | ||
# Modify the `ns` key of the token to control what namespaces they can access. | ||
|
||
schema := { | ||
"$schema": "http://json-schema.org/draft-07/schema#", | ||
"type": "object", | ||
"properties": { | ||
"username": {"type": "string"}, | ||
"password": {"type": "string", "writeOnly": true}, | ||
}, | ||
"required": ["username", "password"], | ||
} | ||
|
||
now := time.now_ns() / 1000 | ||
|
||
one_month := time.add_date(time.now_ns(), 0, 1, 0) / 1000 | ||
|
||
# userlist should be a map of usernames to scrypt-hashed passwords and salts. in | ||
# a simple live setup they can be hard coded here as a map, and then apply | ||
# appropriate file permissions to this policy. | ||
userlist := { | ||
# "username": ["hash", "salt"] | ||
} | ||
|
||
valid_user := input.ask.username if { | ||
input.ask.username in userlist | ||
|
||
hash := userlist[input.ask.username][0] | ||
salt := userlist[input.ask.username][1] | ||
hash == scrypt(input.ask.password, salt) | ||
} | ||
|
||
token := io.jwt.encode_sign( | ||
{ | ||
"typ": "JWT", | ||
"alg": "RS256", | ||
}, | ||
{ | ||
"iss": input.nodeId, | ||
"sub": valid_user, | ||
"aud": [input.nodeId], | ||
"iat": now, | ||
"exp": one_month, | ||
"ns": { | ||
# Read-only access to all namespaces | ||
"*": read_only, | ||
# Writable access to own namespace | ||
valid_user: full_access, | ||
}, | ||
}, | ||
input.signingKey, | ||
) | ||
|
||
namespace_read := 1 | ||
namespace_write := 2 | ||
namespace_download := 4 | ||
namespace_cancel := 8 | ||
|
||
read_only := bits.and(namespace_read, namespace_download) | ||
full_access := bits.and(bits.and(namespace_write, namespace_cancel), read_only) |
Oops, something went wrong.