-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds SnapRefreshRPC and node token authentication (#636)
Adds SnapRefreshRPC and node token authentication --------- Co-authored-by: Angelos Kolaitis <angelos.kolaitis@canonical.com>
- Loading branch information
1 parent
fc2902f
commit 74d645a
Showing
16 changed files
with
394 additions
and
15 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
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,30 @@ | ||
package snapd | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
"net/http" | ||
) | ||
|
||
var socketPath = "/run/snapd.socket" | ||
|
||
type Client struct { | ||
client *http.Client | ||
} | ||
|
||
func NewClient() (*Client, error) { | ||
defaultTransport, ok := http.DefaultTransport.(*http.Transport) | ||
if !ok { | ||
return nil, fmt.Errorf("http.DefaultTransport is not a *http.Transport") | ||
} | ||
|
||
unixTransport := defaultTransport.Clone() | ||
defaultDialContext := unixTransport.DialContext | ||
|
||
unixTransport.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) { | ||
return defaultDialContext(ctx, "unix", socketPath) | ||
} | ||
|
||
return &Client{client: &http.Client{Transport: unixTransport}}, nil | ||
} |
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,32 @@ | ||
package snapd | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
|
||
"github.com/canonical/k8s/pkg/k8sd/types" | ||
) | ||
|
||
type snapdChangeResponse struct { | ||
Result types.RefreshStatus `json:"result"` | ||
} | ||
|
||
func (c *Client) GetRefreshStatus(changeID string) (*types.RefreshStatus, error) { | ||
resp, err := c.client.Get(fmt.Sprintf("http://localhost/v2/changes/%s", changeID)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get snapd change status: %w", err) | ||
} | ||
|
||
resBody, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, fmt.Errorf("client: could not read response body: %s", err) | ||
} | ||
|
||
var changeResponse snapdChangeResponse | ||
if err := json.Unmarshal(resBody, &changeResponse); err != nil { | ||
return nil, fmt.Errorf("client: could not unmarshal response body: %s", err) | ||
} | ||
|
||
return &changeResponse.Result, nil | ||
} |
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,33 @@ | ||
package api | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/v3/state" | ||
) | ||
|
||
func (e *Endpoints) ValidateNodeTokenAccessHandler(tokenHeaderName string) func(s state.State, r *http.Request) (bool, response.Response) { | ||
return func(s state.State, r *http.Request) (bool, response.Response) { | ||
token := r.Header.Get(tokenHeaderName) | ||
if token == "" { | ||
return false, response.Unauthorized(fmt.Errorf("missing header %q", tokenHeaderName)) | ||
} | ||
|
||
snap := e.provider.Snap() | ||
|
||
nodeToken, err := os.ReadFile(snap.NodeTokenFile()) | ||
if err != nil { | ||
return false, response.InternalError(fmt.Errorf("failed to read node access token: %w", err)) | ||
} | ||
|
||
if strings.TrimSpace(string(nodeToken)) != token { | ||
return false, response.Unauthorized(fmt.Errorf("invalid token")) | ||
} | ||
|
||
return true, nil | ||
} | ||
} |
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,31 @@ | ||
package api | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
|
||
apiv1 "github.com/canonical/k8s-snap-api/api/v1" | ||
"github.com/canonical/k8s/pkg/k8sd/types" | ||
"github.com/canonical/k8s/pkg/utils" | ||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/v3/state" | ||
) | ||
|
||
func (e *Endpoints) postSnapRefresh(s state.State, r *http.Request) response.Response { | ||
req := apiv1.SnapRefreshRequest{} | ||
if err := utils.NewStrictJSONDecoder(r.Body).Decode(&req); err != nil { | ||
return response.BadRequest(fmt.Errorf("failed to parse request: %w", err)) | ||
} | ||
|
||
refreshOpts, err := types.RefreshOptsFromAPI(req) | ||
if err != nil { | ||
return response.BadRequest(fmt.Errorf("invalid refresh options: %w", err)) | ||
} | ||
|
||
id, err := e.provider.Snap().Refresh(e.Context(), refreshOpts) | ||
if err != nil { | ||
return response.InternalError(fmt.Errorf("failed to refresh snap: %w", err)) | ||
} | ||
|
||
return response.SyncResponse(true, apiv1.SnapRefreshResponse{ChangeID: id}) | ||
} |
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,25 @@ | ||
package api | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
|
||
apiv1 "github.com/canonical/k8s-snap-api/api/v1" | ||
"github.com/canonical/k8s/pkg/utils" | ||
"github.com/canonical/lxd/lxd/response" | ||
"github.com/canonical/microcluster/v3/state" | ||
) | ||
|
||
func (e *Endpoints) postSnapRefreshStatus(s state.State, r *http.Request) response.Response { | ||
req := apiv1.SnapRefreshStatusRequest{} | ||
if err := utils.NewStrictJSONDecoder(r.Body).Decode(&req); err != nil { | ||
return response.BadRequest(fmt.Errorf("failed to parse request: %w", err)) | ||
} | ||
|
||
status, err := e.provider.Snap().RefreshStatus(e.Context(), req.ChangeID) | ||
if err != nil { | ||
return response.InternalError(fmt.Errorf("failed to get snap refresh status: %w", err)) | ||
} | ||
|
||
return response.SyncResponse(true, status.ToAPI()) | ||
} |
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,46 @@ | ||
package types | ||
|
||
import ( | ||
"fmt" | ||
|
||
apiv1 "github.com/canonical/k8s-snap-api/api/v1" | ||
) | ||
|
||
// RefreshOpts controls the target version of the snap during a refresh. | ||
type RefreshOpts struct { | ||
// LocalPath refreshes the snap using a local snap archive, e.g. "/path/to/k8s.snap". | ||
LocalPath string `json:"localPath"` | ||
// Channel refreshes the snap to track a specific channel, e.g. "latest/edge". | ||
Channel string `json:"channel"` | ||
// Revision refreshes the snap to a specific revision, e.g. "722". | ||
Revision string `json:"revision"` | ||
} | ||
|
||
func RefreshOptsFromAPI(req apiv1.SnapRefreshRequest) (RefreshOpts, error) { | ||
var optsMap = map[string]string{ | ||
"localPath": req.LocalPath, | ||
"channel": req.Channel, | ||
"revision": req.Revision, | ||
} | ||
|
||
// Make sure only one of the options is set. | ||
alreadySet := false | ||
for _, v := range optsMap { | ||
if alreadySet && v != "" { | ||
return RefreshOpts{}, fmt.Errorf("only one of localPath, channel or revision can be specified") | ||
} | ||
if v != "" { | ||
alreadySet = true | ||
} | ||
} | ||
|
||
switch { | ||
case req.LocalPath != "": | ||
return RefreshOpts{LocalPath: req.LocalPath}, nil | ||
case req.Channel != "": | ||
return RefreshOpts{Channel: req.Channel}, nil | ||
case req.Revision != "": | ||
return RefreshOpts{Revision: req.Revision}, nil | ||
} | ||
return RefreshOpts{}, fmt.Errorf("empty snap refresh target") | ||
} |
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,24 @@ | ||
package types | ||
|
||
import ( | ||
apiv1 "github.com/canonical/k8s-snap-api/api/v1" | ||
) | ||
|
||
// RefreshStatus represents the status of a snap refresh operation. | ||
// This is a partial struct derived from the Change struct used by the snapd API. | ||
type RefreshStatus struct { | ||
// Status is the current status of the operation. | ||
Status string `json:"status"` | ||
// Ready indicates whether the operation has completed. | ||
Ready bool `json:"ready"` | ||
// Err contains an error message if the operation failed. | ||
Err string `json:"err,omitempty"` | ||
} | ||
|
||
func (r RefreshStatus) ToAPI() apiv1.SnapRefreshStatusResponse { | ||
return apiv1.SnapRefreshStatusResponse{ | ||
Status: r.Status, | ||
Completed: r.Ready, | ||
ErrorMessage: r.Err, | ||
} | ||
} |
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
Oops, something went wrong.