-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into grady/startrunbe
- Loading branch information
Showing
26 changed files
with
1,014 additions
and
27 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,47 +103,49 @@ func (c *Client) DeleteBlob(ctx context.Context, uri string) error { | |
|
||
// SignedUploadURL returns a URL that is allowed to upload to the given URI. | ||
// See https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/storage/[email protected]/sas#example-package-UserDelegationSAS | ||
func (c *Client) SignedUploadURL(ctx context.Context, uri string) (string, error) { | ||
func (c *Client) SignedUploadURL(ctx context.Context, uri string) (string, time.Time, error) { | ||
return c.signBlob(ctx, uri, &sas.BlobPermissions{Create: true, Write: true}) | ||
} | ||
|
||
// SignedDownloadURL returns a URL that is allowed to download the file at the given URI. | ||
// See https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/storage/[email protected]/sas#example-package-UserDelegationSAS | ||
func (c *Client) SignedDownloadURL(ctx context.Context, uri string) (string, error) { | ||
func (c *Client) SignedDownloadURL(ctx context.Context, uri string) (string, time.Time, error) { | ||
return c.signBlob(ctx, uri, &sas.BlobPermissions{Read: true}) | ||
} | ||
|
||
func (c *Client) signBlob(ctx context.Context, uri string, perms *sas.BlobPermissions) (string, error) { | ||
func (c *Client) signBlob(ctx context.Context, uri string, perms *sas.BlobPermissions) (string, time.Time, error) { | ||
ctr, blb, ok := blob.SplitURI(Scheme, uri) | ||
if !ok { | ||
return "", fmt.Errorf("malformed URI %q is not for Azure", uri) | ||
return "", time.Time{}, fmt.Errorf("malformed URI %q is not for Azure", uri) | ||
} | ||
|
||
// The blob component is important, otherwise the signed URL is applicable to the whole container. | ||
if blb == "" { | ||
return "", fmt.Errorf("uri %q did not contain a blob component", uri) | ||
return "", time.Time{}, fmt.Errorf("uri %q did not contain a blob component", uri) | ||
} | ||
|
||
now := c.now().UTC().Add(-10 * time.Second) | ||
udc, err := c.getUserDelegationCredential(ctx, now) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get udc: %w", err) | ||
return "", time.Time{}, fmt.Errorf("failed to get udc: %w", err) | ||
} | ||
|
||
expiry := now.Add(15 * time.Minute) | ||
|
||
// Create Blob Signature Values with desired permissions and sign with user delegation credential | ||
sasQueryParams, err := sas.BlobSignatureValues{ | ||
Protocol: sas.ProtocolHTTPS, | ||
StartTime: now, | ||
ExpiryTime: now.Add(15 * time.Minute), | ||
ExpiryTime: expiry, | ||
Permissions: perms.String(), | ||
ContainerName: ctr, | ||
BlobName: blb, | ||
}.SignWithUserDelegation(udc) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to sign blob: %w", err) | ||
return "", time.Time{}, fmt.Errorf("failed to sign blob: %w", err) | ||
} | ||
|
||
return fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s?%s", c.storageAccount, ctr, blb, sasQueryParams.Encode()), nil | ||
return fmt.Sprintf("https://%s.blob.core.windows.net/%s/%s?%s", c.storageAccount, ctr, blb, sasQueryParams.Encode()), expiry, nil | ||
} | ||
|
||
func (c *Client) ListBlobs(ctx context.Context, uriPrefix string) ([]string, error) { | ||
|
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,39 @@ | ||
package pactasrv | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/RMI/pacta/cmd/server/pactasrv/conv" | ||
"github.com/RMI/pacta/oapierr" | ||
api "github.com/RMI/pacta/openapi/pacta" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// queries the platform's audit logs | ||
// (POST /audit-logs) | ||
func (s *Server) ListAuditLogs(ctx context.Context, request api.ListAuditLogsRequestObject) (api.ListAuditLogsResponseObject, error) { | ||
// TODO(#12) implement authorization | ||
query, err := conv.AuditLogQueryFromOAPI(request.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// TODO(#12) implement additional authorizations, ensuring for example that: | ||
// - every generated query has reasonable limits + only filters by allowed search terms | ||
// - the actor is allowed to see the audit logs of the actor_owner, but not of other actor_owners | ||
// - initiative admins should be able to see audit logs of the initiative, but not initiative members | ||
// - admins should be able to see all | ||
// This is probably our most important piece of authz-ery, so it should be thoroughly tested. | ||
als, pi, err := s.DB.AuditLogs(s.DB.NoTxn(ctx), query) | ||
if err != nil { | ||
return nil, oapierr.Internal("querying audit logs failed", zap.Error(err)) | ||
} | ||
results, err := dereference(conv.AuditLogsToOAPI(als)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return api.ListAuditLogs200JSONResponse{ | ||
AuditLogs: results, | ||
Cursor: string(pi.Cursor), | ||
HasNextPage: pi.HasNextPage, | ||
}, 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,103 @@ | ||
package pactasrv | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/RMI/pacta/db" | ||
"github.com/RMI/pacta/oapierr" | ||
api "github.com/RMI/pacta/openapi/pacta" | ||
"github.com/RMI/pacta/pacta" | ||
"go.uber.org/zap" | ||
) | ||
|
||
func (s *Server) AccessBlobContent(ctx context.Context, request api.AccessBlobContentRequestObject) (api.AccessBlobContentResponseObject, error) { | ||
actorInfo, err := s.getActorInfoOrFail(ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
blobIDs := []pacta.BlobID{} | ||
for _, item := range request.Body.Items { | ||
blobIDs = append(blobIDs, pacta.BlobID(item.BlobId)) | ||
} | ||
err404 := oapierr.NotFound("blob not found", zap.Strings("blob_ids", asStrs(blobIDs))) | ||
bos, err := s.DB.BlobContexts(s.DB.NoTxn(ctx), blobIDs) | ||
if err != nil { | ||
if db.IsNotFound(err) { | ||
return nil, err404 | ||
} | ||
return nil, oapierr.Internal("error getting blob owners", zap.Error(err), zap.Strings("blob_ids", asStrs(blobIDs))) | ||
} | ||
asMap := map[pacta.BlobID]*pacta.BlobContext{} | ||
for _, boi := range bos { | ||
asMap[boi.BlobID] = boi | ||
} | ||
auditLogs := []*pacta.AuditLog{} | ||
for _, blobID := range blobIDs { | ||
boi := asMap[blobID] | ||
accessAsOwner := boi.PrimaryTargetOwnerID == actorInfo.OwnerID | ||
accessAsAdmin := boi.AdminDebugEnabled && actorInfo.IsAdmin | ||
accessAsSuperAdmin := boi.AdminDebugEnabled && actorInfo.IsSuperAdmin | ||
var actorType pacta.AuditLogActorType | ||
if accessAsOwner { | ||
actorType = pacta.AuditLogActorType_Owner | ||
} else if accessAsAdmin { | ||
actorType = pacta.AuditLogActorType_Admin | ||
} else if accessAsSuperAdmin { | ||
actorType = pacta.AuditLogActorType_SuperAdmin | ||
} else { | ||
// DENY CASE | ||
return nil, err404 | ||
} | ||
auditLogs = append(auditLogs, &pacta.AuditLog{ | ||
Action: pacta.AuditLogAction_Download, | ||
ActorID: string(actorInfo.UserID), | ||
ActorOwner: &pacta.Owner{ID: actorInfo.OwnerID}, | ||
ActorType: actorType, | ||
PrimaryTargetType: boi.PrimaryTargetType, | ||
PrimaryTargetID: boi.PrimaryTargetID, | ||
PrimaryTargetOwner: &pacta.Owner{ID: boi.PrimaryTargetOwnerID}, | ||
}) | ||
} | ||
|
||
blobs, err := s.DB.Blobs(s.DB.NoTxn(ctx), blobIDs) | ||
if err != nil { | ||
if db.IsNotFound(err) { | ||
return nil, err404 | ||
} | ||
return nil, oapierr.Internal("error getting blobs", zap.Error(err), zap.Strings("blob_ids", asStrs(blobIDs))) | ||
} | ||
|
||
err = s.DB.Transactional(ctx, func(tx db.Tx) error { | ||
for i, al := range auditLogs { | ||
_, err := s.DB.CreateAuditLog(tx, al) | ||
if err != nil { | ||
return fmt.Errorf("creating audit log %d/%d: %w", i+1, len(auditLogs), err) | ||
} | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return nil, oapierr.Internal("error creating audit logs - no download URLs generated", zap.Error(err), zap.Strings("blob_ids", asStrs(blobIDs))) | ||
} | ||
|
||
// Note, we're not parallelizing this because it is probably not nescessary. | ||
// The majority use case of this endpoint will be the user clicking a download | ||
// button, which will spin as it gets the URL, then turn into a dial as the | ||
// download starts. That allows us to only generate audit logs for true accesses, | ||
// and will typically happen on a single-file basis. | ||
response := api.AccessBlobContentResp{} | ||
for _, blob := range blobs { | ||
url, expiryTime, err := s.Blob.SignedDownloadURL(ctx, string(blob.BlobURI)) | ||
if err != nil { | ||
return nil, oapierr.Internal("error getting signed download url", zap.Error(err), zap.String("blob_uri", string(blob.BlobURI))) | ||
} | ||
response.Items = append(response.Items, api.AccessBlobContentRespItem{ | ||
BlobId: string(blob.ID), | ||
DownloadUrl: url, | ||
ExpirationTime: expiryTime, | ||
}) | ||
} | ||
return api.AccessBlobContent200JSONResponse(response), 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
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.