-
Notifications
You must be signed in to change notification settings - Fork 930
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
lxc: Prevent accept-certificate flag when using trust token #14149
base: main
Are you sure you want to change the base?
Changes from 8 commits
ed62fad
8fbab5e
736a3b3
f2a8318
f6ba1b1
9eef869
8957abb
f644132
639991a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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.
Is this the current state now and what you've fixed, or what you are changing it to be? I struggled to understand the change here. 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. Because:
Sounds like the wrong behaviour to me, so wanted to understand if this is new or what is being fixed? 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. This is the current behavior. If remote is removed, and readded, it will fail when trying to add client certificate into remote's trust store, because it is already present there - failing with IMO, we should instead just continue and add the remote. Example of current behaviour (LXD running in a VM $ lxc ls
+------+---------+------------------------+-------------------------------------------------+-----------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+---------+------------------------+-------------------------------------------------+-----------------+-----------+
| v2 | RUNNING | 10.70.164.239 (enp5s0) | fd42:2bf4:ef35:8266:216:3eff:fea8:9da4 (enp5s0) | VIRTUAL-MACHINE | 0 |
+------+---------+------------------------+-------------------------------------------------+-----------------+-----------+
# Add the remote (initially).
$ token=$(lxc exec v2 -- lxc config trust add --name test --quiet)
$ lxc remote add test $token
# Remove the remote.
$ lxc remote rm test
# Add the remote (again). The client certificate is already present in remote's trust store.
$ token=$(lxc exec v2 -- lxc config trust add --name test --quiet)
$ lxc remote add test $token
Error: Failed to create certificate: Client is already trusted 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. Does 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. Yes, if target URL is specified: lxc remote add 10.70.164.239
# or
lxc remote add test 10.70.164.239 When token is used, the address is extracted from the token itself (if specific URL is not provided), so it should work even when token is provided. 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. Got you. Will the new token still get expired once its used (even though its not really used)? 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. No, the token is not consumed. 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. Should it not be? From the user's PoV we've used it. 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. Yes, I think that makes sense. Will fix that. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -153,7 +153,7 @@ func (c *cmdRemoteAdd) findProject(d lxd.InstanceServer, project string) (string | |
return project, nil | ||
} | ||
|
||
func (c *cmdRemoteAdd) runToken(server string, token string, rawToken *api.CertificateAddToken) error { | ||
func (c *cmdRemoteAdd) runToken(addr string, server string, token string, rawToken *api.CertificateAddToken) error { | ||
conf := c.global.conf | ||
|
||
if !conf.HasClientCertificate() { | ||
|
@@ -164,6 +164,12 @@ func (c *cmdRemoteAdd) runToken(server string, token string, rawToken *api.Certi | |
} | ||
} | ||
|
||
// If address is provided, use token on that specific address. | ||
if addr != "" { | ||
return c.addRemoteFromToken(addr, server, token, rawToken.Fingerprint) | ||
} | ||
|
||
// Otherwise, iterate over all addresses within the token. | ||
for _, addr := range rawToken.Addresses { | ||
addr = fmt.Sprintf("https://%s", addr) | ||
|
||
|
@@ -179,6 +185,7 @@ func (c *cmdRemoteAdd) runToken(server string, token string, rawToken *api.Certi | |
return nil | ||
} | ||
|
||
// Finally, fallback to manual input. | ||
fmt.Println(i18n.G("All server addresses are unavailable")) | ||
fmt.Print(i18n.G("Please provide an alternate server address (empty to abort):") + " ") | ||
|
||
|
@@ -248,16 +255,34 @@ func (c *cmdRemoteAdd) addRemoteFromToken(addr string, server string, token stri | |
return api.StatusErrorf(http.StatusServiceUnavailable, "%s: %w", i18n.G("Unavailable remote server"), err) | ||
} | ||
|
||
req := api.CertificatesPost{} | ||
if d.HasExtension("explicit_trust_token") { | ||
req.TrustToken = token | ||
} else { | ||
req.Password = token | ||
srv, _, err := d.GetServer() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = d.CreateCertificate(req) | ||
if err != nil { | ||
return fmt.Errorf(i18n.G("Failed to create certificate: %w"), err) | ||
if srv.Auth != "trusted" { | ||
req := api.CertificatesPost{} | ||
if d.HasExtension("explicit_trust_token") { | ||
req.TrustToken = token | ||
} else { | ||
req.Password = token | ||
} | ||
|
||
// Add client certificate to trust store. | ||
err = d.CreateCertificate(req) | ||
if err != nil { | ||
return fmt.Errorf(i18n.G("Failed to create certificate: %w"), err) | ||
} | ||
|
||
// And check if trusted now. | ||
srv, _, err = d.GetServer() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if srv.Auth != "trusted" { | ||
return errors.New(i18n.G("Server doesn't trust us after authentication")) | ||
} | ||
} | ||
|
||
// Handle project. | ||
|
@@ -294,6 +319,21 @@ func (c *cmdRemoteAdd) run(cmd *cobra.Command, args []string) error { | |
return errors.New(i18n.G("Remote address must not be empty")) | ||
} | ||
|
||
// Trust token cannot be used when auth type is set to OIDC. | ||
if c.flagToken != "" && c.flagAuthType == "oidc" { | ||
return errors.New(i18n.G("Trust token cannot be used with OIDC authentication")) | ||
} | ||
|
||
// Trust token cannot be used for public remotes. | ||
if c.flagToken != "" && c.flagPublic { | ||
return errors.New(i18n.G("Trust token cannot be used for public remotes")) | ||
} | ||
|
||
// Certificate cannot be blindly accepted when using a trust token. | ||
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. Please can you update the comment to explain the why of this statement. |
||
if c.flagToken != "" && c.flagAcceptCert { | ||
return errors.New(i18n.G("The --accept-certificate flag is not supported when adding a remote using a trust token")) | ||
} | ||
|
||
// Validate the server name. | ||
if strings.Contains(server, ":") { | ||
return errors.New(i18n.G("Remote names may not contain colons")) | ||
|
@@ -319,9 +359,16 @@ func (c *cmdRemoteAdd) run(cmd *cobra.Command, args []string) error { | |
conf.Remotes = map[string]config.Remote{} | ||
} | ||
|
||
// Check if the first argument is a trust token. In such case, we need to | ||
// decode it and use it to connect to the remote. | ||
rawToken, err := shared.CertificateTokenDecode(addr) | ||
if err == nil { | ||
return c.runToken(server, addr, rawToken) | ||
// Certificate cannot be blindly accepted when using a trust token. | ||
if c.flagAcceptCert { | ||
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. can we re-structure this commit to avoid needing to check the same logic twice and duplicating the error message? |
||
return errors.New(i18n.G("The --accept-certificate flag is not supported when adding a remote using a trust token")) | ||
} | ||
|
||
return c.runToken("", server, addr, rawToken) | ||
} | ||
|
||
// Complex remote URL parsing | ||
|
@@ -437,6 +484,16 @@ func (c *cmdRemoteAdd) run(cmd *cobra.Command, args []string) error { | |
return conf.SaveConfig(c.global.confPath) | ||
} | ||
|
||
// Handle adding a remote with trust token. | ||
if c.flagToken != "" { | ||
rawToken, err := shared.CertificateTokenDecode(c.flagToken) | ||
if err != nil { | ||
return fmt.Errorf(i18n.G("Failed to decode trust token: %w"), err) | ||
} | ||
|
||
return c.runToken(addr, server, c.flagToken, rawToken) | ||
} | ||
|
||
// Check if the system CA worked for the TLS connection | ||
var certificate *x509.Certificate | ||
if err != nil { | ||
|
@@ -449,22 +506,32 @@ func (c *cmdRemoteAdd) run(cmd *cobra.Command, args []string) error { | |
|
||
// Handle certificate prompt | ||
if certificate != nil { | ||
// Prompt for certificate acceptance if user did not allow us to blindly | ||
// accept the remote certificate. | ||
if !c.flagAcceptCert { | ||
digest := shared.CertFingerprint(certificate) | ||
|
||
fmt.Printf("%s: %s\n", i18n.G("Certificate fingerprint"), digest) | ||
fmt.Print(i18n.G("ok (y/n/[fingerprint])?") + " ") | ||
line, err := shared.ReadStdin() | ||
if err != nil { | ||
return err | ||
} | ||
for { | ||
line, err := shared.ReadStdin() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if string(line) != digest { | ||
// Continue with adding the remote if digest matches, or the user | ||
// confirmed a fingerprint. | ||
if string(line) == digest || strings.ToLower(string(line[0])) == i18n.G("y") { | ||
break | ||
} | ||
|
||
// Error out if the user didn't confirm the fingerprint. | ||
if len(line) < 1 || strings.ToLower(string(line[0])) == i18n.G("n") { | ||
return errors.New(i18n.G("Server certificate NACKed by user")) | ||
} else if strings.ToLower(string(line[0])) != i18n.G("y") { | ||
return errors.New(i18n.G("Please type 'y', 'n' or the fingerprint:")) | ||
} | ||
|
||
// Ask again for any other invalid input. | ||
fmt.Print(i18n.G("Please type 'y', 'n' or the fingerprint:")) | ||
} | ||
} | ||
|
||
|
@@ -567,11 +634,9 @@ func (c *cmdRemoteAdd) run(cmd *cobra.Command, args []string) error { | |
// use the token instead and prompt for it if not present. | ||
if d.(lxd.InstanceServer).HasExtension("explicit_trust_token") && c.flagPassword == "" { | ||
// Prompt for trust token. | ||
if c.flagToken == "" { | ||
c.flagToken, err = c.global.asker.AskString(fmt.Sprintf(i18n.G("Trust token for %s: "), server), "", nil) | ||
if err != nil { | ||
return err | ||
} | ||
c.flagToken, err = c.global.asker.AskString(fmt.Sprintf(i18n.G("Trust token for %s: "), server), "", nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
req.TrustToken = c.flagToken | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -250,7 +250,7 @@ test_clustering_membership() { | |
|
||
# Client certificate are shared across all nodes. | ||
token="$(LXD_DIR=${LXD_ONE_DIR} lxc config trust add --name foo -q)" | ||
lxc remote add cluster 10.1.1.101:8443 --accept-certificate --token="${token}" | ||
lxc remote add cluster 10.1.1.101:8443 --token="${token}" | ||
lxc remote set-url cluster https://10.1.1.102:8443 | ||
lxc network list cluster: | grep -q "${bridge}" | ||
lxc remote remove cluster | ||
|
@@ -1895,7 +1895,7 @@ test_clustering_address() { | |
# that the REST API is exposed. | ||
url="https://10.1.1.101:8443" | ||
token="$(LXD_DIR="${LXD_ONE_DIR}" lxc config trust add --name foo --quiet)" | ||
lxc remote add cluster --token "${token}" --accept-certificate "${url}" | ||
lxc remote add cluster --token "${token}" "${url}" | ||
lxc storage list cluster: | grep -q data | ||
|
||
# Add a newline at the end of each line. YAML as weird rules.. | ||
|
@@ -2763,10 +2763,9 @@ test_clustering_image_refresh() { | |
LXD_DIR="${LXD_REMOTE_DIR}" lxc config set core.https_address "10.1.1.104:8443" | ||
|
||
# Add remotes | ||
lxc remote add public "https://10.1.1.104:8443" --accept-certificate --public | ||
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. Can we add tests for not being able to use --accept-certificate with tokens please? |
||
token="$(LXD_DIR="${LXD_ONE_DIR}" lxc config trust add --name foo --quiet)" | ||
lxc remote add public "https://10.1.1.104:8443" --accept-certificate --token foo --public | ||
token="$(LXD_DIR="${LXD_ONE_DIR}" lxc config trust add --name foo --quiet)" | ||
lxc remote add cluster "https://10.1.1.101:8443" --accept-certificate --token "${token}" | ||
lxc remote add cluster "https://10.1.1.101:8443" --token "${token}" | ||
|
||
LXD_DIR="${LXD_REMOTE_DIR}" lxc init testimage c1 | ||
|
||
|
@@ -3461,7 +3460,7 @@ test_clustering_groups() { | |
spawn_lxd_and_join_cluster "${ns3}" "${bridge}" "${cert}" 3 1 "${LXD_THREE_DIR}" "${LXD_ONE_DIR}" | ||
|
||
token="$(LXD_DIR="${LXD_ONE_DIR}" lxc config trust add --name foo --quiet)" | ||
lxc remote add cluster --token "${token}" --accept-certificate "https://10.1.1.101:8443" | ||
lxc remote add cluster --token "${token}" "https://10.1.1.101:8443" | ||
|
||
# Initially, there is only the default group | ||
lxc cluster group show cluster:default | ||
|
@@ -3929,7 +3928,7 @@ test_clustering_trust_add() { | |
# and query LXD_ONE for it. LXD_TWO should cancel the operation by sending a DELETE /1.0/operations/{uuid} to LXD_ONE | ||
# and needs to parse the metadata of the operation into the correct type to complete the trust process. | ||
# The expiry time should be parsed and found to be expired so the add action should fail. | ||
! lxc remote add lxd_two "${lxd_two_address}" --accept-certificate --token "${lxd_one_token}" || false | ||
! lxc remote add lxd_two "${lxd_two_address}" --token "${lxd_one_token}" || false | ||
|
||
# Expect the operation to be cancelled. | ||
LXD_DIR="${LXD_ONE_DIR}" lxc operation list --format csv | grep -qF "${operation_uuid},TOKEN,Executing operation,CANCELLED" | ||
|
@@ -3955,7 +3954,7 @@ test_clustering_trust_add() { | |
# LXD_TWO does not have the operation running locally, so it should find the UUID of the operation in the database | ||
# and query LXD_ONE for it. LXD_TWO should cancel the operation by sending a DELETE /1.0/operations/{uuid} to LXD_ONE | ||
# and needs to parse the metadata of the operation into the correct type to complete the trust process. | ||
lxc remote add lxd_two "${lxd_two_address}" --accept-certificate --token "${lxd_one_token}" | ||
lxc remote add lxd_two "${lxd_two_address}" --token "${lxd_one_token}" | ||
|
||
# Expect the operation to be cancelled. | ||
LXD_DIR="${LXD_ONE_DIR}" lxc operation list --format csv | grep -qF "${operation_uuid},TOKEN,Executing operation,CANCELLED" | ||
|
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.
Please can you add more detail to the commit message explaining what you fixed.