Skip to content

Adding checks during SAS creation #20379

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

Merged
merged 5 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions sdk/storage/azblob/sas/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ type AccountSignatureValues struct {
Protocol Protocol `param:"spr"` // See the SASProtocol* constants
StartTime time.Time `param:"st"` // Not specified if IsZero
ExpiryTime time.Time `param:"se"` // Not specified if IsZero
Permissions string `param:"sp"` // Create by initializing a AccountSASPermissions and then call String()
Permissions string `param:"sp"` // Create by initializing AccountPermissions and then call String()
IPRange IPRange `param:"sip"`
ResourceTypes string `param:"srt"` // Create by initializing AccountSASResourceTypes and then call String()
ResourceTypes string `param:"srt"` // Create by initializing AccountResourceTypes and then call String()
}

// SignWithSharedKey uses an account's shared key credential to sign this signature values to produce
Expand All @@ -50,6 +50,12 @@ func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKey
}
v.Permissions = perms.String()

resources, err := parseAccountResourceTypes(v.ResourceTypes)
if err != nil {
return QueryParameters{}, err
}
v.ResourceTypes = resources.String()

startTime, expiryTime, _ := formatTimesForSigning(v.StartTime, v.ExpiryTime, time.Time{})

stringToSign := strings.Join([]string{
Expand Down Expand Up @@ -90,13 +96,13 @@ func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKey
}

// AccountPermissions type simplifies creating the permissions string for an Azure Storage Account SAS.
// Initialize an instance of this type and then call its String method to set AccountSASSignature value's Permissions field.
// Initialize an instance of this type and then call its String method to set AccountSignatureValues' Permissions field.
type AccountPermissions struct {
Read, Write, Delete, DeletePreviousVersion, PermanentDelete, List, Add, Create, Update, Process, FilterByTags, Tag, SetImmutabilityPolicy bool
}

// String produces the SAS permissions string for an Azure Storage account.
// Call this method to set AccountSASSignatureValues' Permissions field.
// Call this method to set AccountSignatureValues' Permissions field.
func (p *AccountPermissions) String() string {
var buffer bytes.Buffer
if p.Read {
Expand Down Expand Up @@ -141,7 +147,7 @@ func (p *AccountPermissions) String() string {
return buffer.String()
}

// Parse initializes the AccountSASPermissions' fields from a string.
// Parse initializes the AccountPermissions' fields from a string.
func parseAccountPermissions(s string) (AccountPermissions, error) {
p := AccountPermissions{} // Clear out the flags
for _, r := range s {
Expand Down Expand Up @@ -180,13 +186,13 @@ func parseAccountPermissions(s string) (AccountPermissions, error) {
}

// AccountResourceTypes type simplifies creating the resource types string for an Azure Storage Account SAS.
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues' ResourceTypes field.
// Initialize an instance of this type and then call its String method to set AccountSignatureValues' ResourceTypes field.
type AccountResourceTypes struct {
Service, Container, Object bool
}

// String produces the SAS resource types string for an Azure Storage account.
// Call this method to set AccountSASSignatureValues' ResourceTypes field.
// Call this method to set AccountSignatureValues' ResourceTypes field.
func (rt *AccountResourceTypes) String() string {
var buffer bytes.Buffer
if rt.Service {
Expand All @@ -200,3 +206,21 @@ func (rt *AccountResourceTypes) String() string {
}
return buffer.String()
}

// parseAccountResourceTypes initializes the AccountResourceTypes' fields from a string.
func parseAccountResourceTypes(s string) (AccountResourceTypes, error) {
rt := AccountResourceTypes{}
for _, r := range s {
switch r {
case 's':
rt.Service = true
case 'c':
rt.Container = true
case 'o':
rt.Object = true
default:
return AccountResourceTypes{}, fmt.Errorf("invalid resource type character: '%v'", r)
}
}
return rt, nil
}
32 changes: 32 additions & 0 deletions sdk/storage/azblob/sas/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,38 @@ func TestAccountResourceTypes_String(t *testing.T) {
}
}

func TestAccountResourceTypes_Parse(t *testing.T) {
testdata := []struct {
input string
expected AccountResourceTypes
}{
{expected: AccountResourceTypes{Service: true}, input: "s"},
{expected: AccountResourceTypes{Container: true}, input: "c"},
{expected: AccountResourceTypes{Object: true}, input: "o"},
{expected: AccountResourceTypes{
Service: true,
Container: true,
Object: true,
}, input: "sco"},
{expected: AccountResourceTypes{
Service: true,
Container: true,
Object: true,
}, input: "osc"},
}
for _, c := range testdata {
permissions, err := parseAccountResourceTypes(c.input)
require.Nil(t, err)
require.Equal(t, c.expected, permissions)
}
}

func TestAccountResourceTypes_ParseNegative(t *testing.T) {
_, err := parseAccountResourceTypes("scoz") // Here 'z' is invalid
require.NotNil(t, err)
require.Contains(t, err.Error(), "122")
}

// TODO: Sign With Shared Key
// Negative Case
// Version not provided
Expand Down
17 changes: 11 additions & 6 deletions sdk/storage/azblob/sas/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package sas

import (
"bytes"
"errors"
"fmt"
"strings"
"time"
Expand All @@ -24,7 +25,7 @@ type BlobSignatureValues struct {
StartTime time.Time `param:"st"` // Not specified if IsZero
ExpiryTime time.Time `param:"se"` // Not specified if IsZero
SnapshotTime time.Time
Permissions string `param:"sp"` // Create by initializing a ContainerSASPermissions or BlobSASPermissions and then call String()
Permissions string `param:"sp"` // Create by initializing ContainerPermissions or BlobPermissions and then call String()
IPRange IPRange `param:"sip"`
Identifier string `param:"si"`
ContainerName string
Expand All @@ -50,8 +51,8 @@ func getDirectoryDepth(path string) string {

// SignWithSharedKey uses an account's SharedKeyCredential to sign this signature values to produce the proper SAS query parameters.
func (v BlobSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKeyCredential) (QueryParameters, error) {
if sharedKeyCredential == nil {
return QueryParameters{}, fmt.Errorf("cannot sign SAS query without Shared Key Credential")
if v.ExpiryTime.IsZero() || v.Permissions == "" {
return QueryParameters{}, errors.New("service SAS is missing at least one of these: ExpiryTime or Permissions")
}

//Make sure the permission characters are in the correct order
Expand Down Expand Up @@ -141,6 +142,10 @@ func (v BlobSignatureValues) SignWithUserDelegation(userDelegationCredential *Us
return QueryParameters{}, fmt.Errorf("cannot sign SAS query without User Delegation Key")
}

if v.ExpiryTime.IsZero() || v.Permissions == "" {
return QueryParameters{}, errors.New("user delegation SAS is missing at least one of these: ExpiryTime or Permissions")
}

// Parse the resource
resource := "c"
if !v.SnapshotTime.IsZero() {
Expand Down Expand Up @@ -261,15 +266,15 @@ func getCanonicalName(account string, containerName string, blobName string, dir
}

// ContainerPermissions type simplifies creating the permissions string for an Azure Storage container SAS.
// Initialize an instance of this type and then call its String method to set BlobSASSignatureValues' Permissions field.
// Initialize an instance of this type and then call its String method to set BlobSignatureValues' Permissions field.
// All permissions descriptions can be found here: https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas#permissions-for-a-directory-container-or-blob
type ContainerPermissions struct {
Read, Add, Create, Write, Delete, DeletePreviousVersion, List, FilterByTags, Move, SetImmutabilityPolicy bool
Execute, ModifyOwnership, ModifyPermissions bool // Meant for hierarchical namespace accounts
}

// String produces the SAS permissions string for an Azure Storage container.
// Call this method to set BlobSASSignatureValues' Permissions field.
// Call this method to set BlobSignatureValues' Permissions field.
func (p *ContainerPermissions) String() string {
var b bytes.Buffer
if p.Read {
Expand Down Expand Up @@ -353,7 +358,7 @@ func parseContainerPermissions(s string) (ContainerPermissions, error) {
}

// BlobPermissions type simplifies creating the permissions string for an Azure Storage blob SAS.
// Initialize an instance of this type and then call its String method to set BlobSASSignatureValues' Permissions field.
// Initialize an instance of this type and then call its String method to set BlobSignatureValues' Permissions field.
type BlobPermissions struct {
Read, Add, Create, Write, Delete, DeletePreviousVersion, PermanentDelete, List, Tag, Move, Execute, Ownership, Permissions, SetImmutabilityPolicy bool
}
Expand Down
38 changes: 31 additions & 7 deletions sdk/storage/azqueue/sas/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ type AccountSignatureValues struct {
Protocol Protocol `param:"spr"` // See the SASProtocol* constants
StartTime time.Time `param:"st"` // Not specified if IsZero
ExpiryTime time.Time `param:"se"` // Not specified if IsZero
Permissions string `param:"sp"` // Create by initializing a AccountSASPermissions and then call String()
Permissions string `param:"sp"` // Create by initializing a AccountPermissions and then call String()
IPRange IPRange `param:"sip"`
ResourceTypes string `param:"srt"` // Create by initializing AccountSASResourceTypes and then call String()
ResourceTypes string `param:"srt"` // Create by initializing AccountResourceTypes and then call String()
}

// SignWithSharedKey uses an account's shared key credential to sign this signature values to produce
Expand All @@ -47,6 +47,12 @@ func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKey
}
v.Permissions = perms.String()

resources, err := parseAccountResourceTypes(v.ResourceTypes)
if err != nil {
return QueryParameters{}, err
}
v.ResourceTypes = resources.String()

startTime, expiryTime := formatTimesForSigning(v.StartTime, v.ExpiryTime)

stringToSign := strings.Join([]string{
Expand Down Expand Up @@ -87,13 +93,13 @@ func (v AccountSignatureValues) SignWithSharedKey(sharedKeyCredential *SharedKey
}

// AccountPermissions type simplifies creating the permissions string for an Azure Storage Account SAS.
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues' Permissions field.
// Initialize an instance of this type and then call its String method to set AccountSignatureValues' Permissions field.
type AccountPermissions struct {
Read, Write, Delete, DeletePreviousVersion, PermanentDelete, List, Add, Create, Update, Process, Tag, FilterByTags, SetImmutabilityPolicy bool
}

// String produces the SAS permissions string for an Azure Storage account.
// Call this method to set AccountSASSignatureValues's Permissions field.
// Call this method to set AccountSignatureValues' Permissions field.
func (p *AccountPermissions) String() string {
var buffer bytes.Buffer
if p.Read {
Expand Down Expand Up @@ -138,7 +144,7 @@ func (p *AccountPermissions) String() string {
return buffer.String()
}

// Parse initializes the AccountSASPermissions' fields from a string.
// Parse initializes the AccountPermissions' fields from a string.
func parseAccountPermissions(s string) (AccountPermissions, error) {
p := AccountPermissions{} // Clear out the flags
for _, r := range s {
Expand Down Expand Up @@ -177,13 +183,13 @@ func parseAccountPermissions(s string) (AccountPermissions, error) {
}

// AccountResourceTypes type simplifies creating the resource types string for an Azure Storage Account SAS.
// Initialize an instance of this type and then call its String method to set AccountSASSignatureValues' ResourceTypes field.
// Initialize an instance of this type and then call its String method to set AccountSignatureValues' ResourceTypes field.
type AccountResourceTypes struct {
Service, Container, Object bool
}

// String produces the SAS resource types string for an Azure Storage account.
// Call this method to set AccountSASSignatureValues' ResourceTypes field.
// Call this method to set AccountSignatureValues' ResourceTypes field.
func (rt *AccountResourceTypes) String() string {
var buffer bytes.Buffer
if rt.Service {
Expand All @@ -197,3 +203,21 @@ func (rt *AccountResourceTypes) String() string {
}
return buffer.String()
}

// parseAccountResourceTypes initializes the AccountResourceTypes' fields from a string.
func parseAccountResourceTypes(s string) (AccountResourceTypes, error) {
rt := AccountResourceTypes{}
for _, r := range s {
switch r {
case 's':
rt.Service = true
case 'c':
rt.Container = true
case 'o':
rt.Object = true
default:
return AccountResourceTypes{}, fmt.Errorf("invalid resource type character: '%v'", r)
}
}
return rt, nil
}
32 changes: 32 additions & 0 deletions sdk/storage/azqueue/sas/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,38 @@ func TestAccountResourceTypes_String(t *testing.T) {
}
}

func TestAccountResourceTypes_Parse(t *testing.T) {
testdata := []struct {
input string
expected AccountResourceTypes
}{
{expected: AccountResourceTypes{Service: true}, input: "s"},
{expected: AccountResourceTypes{Container: true}, input: "c"},
{expected: AccountResourceTypes{Object: true}, input: "o"},
{expected: AccountResourceTypes{
Service: true,
Container: true,
Object: true,
}, input: "sco"},
{expected: AccountResourceTypes{
Service: true,
Container: true,
Object: true,
}, input: "osc"},
}
for _, c := range testdata {
permissions, err := parseAccountResourceTypes(c.input)
require.Nil(t, err)
require.Equal(t, c.expected, permissions)
}
}

func TestAccountResourceTypes_ParseNegative(t *testing.T) {
_, err := parseAccountResourceTypes("scoz") // Here 'z' is invalid
require.NotNil(t, err)
require.Contains(t, err.Error(), "122")
}

// TODO: Sign With Shared Key
// Negative Case
// Version not provided
Expand Down
6 changes: 3 additions & 3 deletions sdk/storage/azqueue/sas/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ func getCanonicalName(account string, queueName string) string {
}

// QueuePermissions type simplifies creating the permissions string for an Azure Storage Queue SAS.
// Initialize an instance of this type and then call its String method to set QueueSASSignatureValues' Permissions field.
// Initialize an instance of this type and then call its String method to set QueueSignatureValues' Permissions field.
type QueuePermissions struct {
Read, Add, Update, Process bool
}

// String produces the SAS permissions string for an Azure Storage Queue.
// Call this method to set QueueSASSignatureValues' Permissions field.
// Call this method to set QueueSignatureValues' Permissions field.
func (p *QueuePermissions) String() string {
var b bytes.Buffer
if p.Read {
Expand All @@ -111,7 +111,7 @@ func (p *QueuePermissions) String() string {
return b.String()
}

// Parse initializes the QueueSASPermissions' fields from a string.
// Parse initializes the QueuePermissions' fields from a string.
func parseQueuePermissions(s string) (QueuePermissions, error) {
p := QueuePermissions{}
for _, r := range s {
Expand Down