Merge pull request #946 from humble00/master

Add TrustId parameter to swift driver
This commit is contained in:
Richard Scothern 2015-09-08 15:59:29 -07:00
commit 7a305cc8cd
10 changed files with 201 additions and 50 deletions

2
Godeps/Godeps.json generated
View file

@ -96,7 +96,7 @@
}, },
{ {
"ImportPath": "github.com/ncw/swift", "ImportPath": "github.com/ncw/swift",
"Rev": "22c8fa9fb5ba145b4d4e2cebb027e84b1a7b1296" "Rev": "ca8cbbde50d4e12dd8ad70b1bd66589ae98efc5c"
}, },
{ {
"ImportPath": "github.com/yvasiyarov/go-metrics", "ImportPath": "github.com/yvasiyarov/go-metrics",

View file

@ -1,4 +1,5 @@
language: go language: go
sudo: false
go: go:
- 1.1.2 - 1.1.2

View file

@ -77,6 +77,10 @@ And optionally these if using v3 authentication
export SWIFT_API_DOMAIN_ID='domain id' export SWIFT_API_DOMAIN_ID='domain id'
export SWIFT_API_DOMAIN='domain name' export SWIFT_API_DOMAIN='domain name'
And optionally these if using v3 trust
export SWIFT_TRUST_ID='TrustId'
And optionally this if you want to skip server certificate validation And optionally this if you want to skip server certificate validation
export SWIFT_AUTH_INSECURE=1 export SWIFT_AUTH_INSECURE=1
@ -126,3 +130,5 @@ Contributors
- Sylvain Baubeau <sbaubeau@redhat.com> - Sylvain Baubeau <sbaubeau@redhat.com>
- Chris Kastorff <encryptio@gmail.com> - Chris Kastorff <encryptio@gmail.com>
- Dai HaoJun <haojun.dai@hp.com> - Dai HaoJun <haojun.dai@hp.com>
- Hua Wang <wanghua.humble@gmail.com>
- Fabian Ruff <fabian@progra.de>

View file

@ -33,6 +33,7 @@ type v3AuthRequest struct {
type v3Scope struct { type v3Scope struct {
Project *v3Project `json:"project,omitempty"` Project *v3Project `json:"project,omitempty"`
Domain *v3Domain `json:"domain,omitempty"` Domain *v3Domain `json:"domain,omitempty"`
Trust *v3Trust `json:"OS-TRUST:trust,omitempty"`
} }
type v3Domain struct { type v3Domain struct {
@ -46,6 +47,10 @@ type v3Project struct {
Domain *v3Domain `json:"domain,omitempty"` Domain *v3Domain `json:"domain,omitempty"`
} }
type v3Trust struct {
Id string `json:"id"`
}
type v3User struct { type v3User struct {
Domain *v3Domain `json:"domain,omitempty"` Domain *v3Domain `json:"domain,omitempty"`
Id string `json:"id,omitempty"` Id string `json:"id,omitempty"`
@ -66,7 +71,12 @@ type v3AuthResponse struct {
Token struct { Token struct {
Expires_At, Issued_At string Expires_At, Issued_At string
Methods []string Methods []string
Roles []map[string]string Roles []struct {
Id, Name string
Links struct {
Self string
}
}
Project struct { Project struct {
Domain struct { Domain struct {
@ -129,7 +139,9 @@ func (auth *v3Auth) Request(c *Connection) (*http.Request, error) {
v3.Auth.Identity.Password.User.Domain = domain v3.Auth.Identity.Password.User.Domain = domain
} }
if c.TenantId != "" || c.Tenant != "" { if c.TrustId != "" {
v3.Auth.Scope = &v3Scope{Trust: &v3Trust{Id: c.TrustId}}
} else if c.TenantId != "" || c.Tenant != "" {
v3.Auth.Scope = &v3Scope{Project: &v3Project{}} v3.Auth.Scope = &v3Scope{Project: &v3Project{}}
@ -159,7 +171,7 @@ func (auth *v3Auth) Request(c *Connection) (*http.Request, error) {
if !strings.HasSuffix(url, "/") { if !strings.HasSuffix(url, "/") {
url += "/" url += "/"
} }
url += "tokens" url += "auth/tokens"
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -3,7 +3,10 @@ package swift
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/sha1"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"hash" "hash"
@ -94,6 +97,7 @@ type Connection struct {
Internal bool // Set this to true to use the the internal / service network Internal bool // Set this to true to use the the internal / service network
Tenant string // Name of the tenant (v2 auth only) Tenant string // Name of the tenant (v2 auth only)
TenantId string // Id of the tenant (v2 auth only) TenantId string // Id of the tenant (v2 auth only)
TrustId string // Id of the trust (v3 auth only)
Transport http.RoundTripper `json:"-" xml:"-"` // Optional specialised http.Transport (eg. for Google Appengine) Transport http.RoundTripper `json:"-" xml:"-"` // Optional specialised http.Transport (eg. for Google Appengine)
// These are filled in after Authenticate is called as are the defaults for above // These are filled in after Authenticate is called as are the defaults for above
StorageUrl string StorageUrl string
@ -1422,8 +1426,10 @@ func (c *Connection) ObjectOpen(container string, objectName string, checkHash b
file.body = io.TeeReader(resp.Body, file.hash) file.body = io.TeeReader(resp.Body, file.hash)
} }
// Read Content-Length // Read Content-Length
file.length, err = getInt64FromHeader(resp, "Content-Length") if resp.Header.Get("Content-Length") != "" {
file.lengthOk = (err == nil) file.length, err = getInt64FromHeader(resp, "Content-Length")
file.lengthOk = (err == nil)
}
return return
} }
@ -1479,6 +1485,16 @@ func (c *Connection) ObjectDelete(container string, objectName string) error {
return err return err
} }
// ObjectTempUrl returns a temporary URL for an object
func (c *Connection) ObjectTempUrl(container string, objectName string, secretKey string, method string, expires time.Time) string {
mac := hmac.New(sha1.New, []byte(secretKey))
prefix, _ := url.Parse(c.StorageUrl)
body := fmt.Sprintf("%s\n%d\n%s/%s/%s", method, expires.Unix(), prefix.Path, container, objectName)
mac.Write([]byte(body))
sig := hex.EncodeToString(mac.Sum(nil))
return fmt.Sprintf("%s/%s/%s?temp_url_sig=%s&temp_url_expires=%d", c.StorageUrl, container, objectName, sig, expires.Unix())
}
// parseResponseStatus parses string like "200 OK" and returns Error. // parseResponseStatus parses string like "200 OK" and returns Error.
// //
// For status codes beween 200 and 299, this returns nil. // For status codes beween 200 and 299, this returns nil.

View file

@ -20,9 +20,8 @@ import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"github.com/ncw/swift"
"github.com/ncw/swift/swifttest"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
@ -30,6 +29,9 @@ import (
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/ncw/swift"
"github.com/ncw/swift/swifttest"
) )
var ( var (
@ -52,6 +54,7 @@ const (
CONTENT_SIZE = int64(len(CONTENTS)) CONTENT_SIZE = int64(len(CONTENTS))
CONTENT_MD5 = "827ccb0eea8a706c4c34a16891f84e7b" CONTENT_MD5 = "827ccb0eea8a706c4c34a16891f84e7b"
EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e" EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e"
SECRET_KEY = "b3968d0207b54ece87cccc06515a89d4"
) )
type someTransport struct{ http.Transport } type someTransport struct{ http.Transport }
@ -211,6 +214,28 @@ func TestV3AuthenticateWithDomainNameAndTenantId(t *testing.T) {
} }
} }
func TestV3TrustWithTrustId(t *testing.T) {
var err error
if !isV3Api() {
return
}
c, err = makeConnection()
if err != nil {
t.Fatal("Failed to create server", err)
}
c.TrustId = os.Getenv("SWIFT_TRUST_ID")
err = c.Authenticate()
if err != nil {
t.Fatal("Auth failed", err)
}
if !c.Authenticated() {
t.Fatal("Not authenticated")
}
}
func TestV3AuthenticateWithDomainIdAndTenantId(t *testing.T) { func TestV3AuthenticateWithDomainIdAndTenantId(t *testing.T) {
var err error var err error
@ -1441,6 +1466,43 @@ func TestObjectDifficultName(t *testing.T) {
} }
} }
func TestTempUrl(t *testing.T) {
err := c.ObjectPutBytes(CONTAINER, OBJECT, []byte(CONTENTS), "")
if err != nil {
t.Fatal(err)
}
m := swift.Metadata{}
m["temp-url-key"] = SECRET_KEY
err = c.AccountUpdate(m.AccountHeaders())
if err != nil {
t.Fatal(err)
}
expiresTime := time.Now().Add(20 * time.Minute)
tempUrl := c.ObjectTempUrl(CONTAINER, OBJECT, SECRET_KEY, "GET", expiresTime)
resp, err := http.Get(tempUrl)
if err != nil {
t.Fatal("Failed to retrieve file from temporary url")
}
if resp.StatusCode == 401 {
t.Log("Server doesn't support tempurl")
} else if resp.StatusCode != 200 {
t.Fatal("HTTP Error retrieving file from temporary url", resp.StatusCode)
} else {
if content, err := ioutil.ReadAll(resp.Body); err != nil || string(content) != CONTENTS {
t.Error("Bad content", err)
}
}
resp.Body.Close()
err = c.ObjectDelete(CONTAINER, OBJECT)
if err != nil {
t.Fatal(err)
}
}
func TestContainerDelete(t *testing.T) { func TestContainerDelete(t *testing.T) {
err := c.ContainerDelete(CONTAINER) err := c.ContainerDelete(CONTAINER)
if err != nil { if err != nil {

View file

@ -8,8 +8,10 @@ package swifttest
import ( import (
"bytes" "bytes"
"crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -33,19 +35,19 @@ import (
) )
const ( const (
DEBUG = false DEBUG = false
TEST_ACCOUNT = "swifttest"
) )
type SwiftServer struct { type SwiftServer struct {
t *testing.T t *testing.T
reqId int reqId int
mu sync.Mutex mu sync.Mutex
Listener net.Listener Listener net.Listener
AuthURL string AuthURL string
URL string URL string
Containers map[string]*container Accounts map[string]*account
Accounts map[string]*account Sessions map[string]*session
Sessions map[string]*session
} }
// The Folder type represents a container stored in an account // The Folder type represents a container stored in an account
@ -96,7 +98,8 @@ type metadata struct {
type account struct { type account struct {
swift.Account swift.Account
metadata metadata
password string password string
Containers map[string]*container
} }
type object struct { type object struct {
@ -294,8 +297,8 @@ func (r containerResource) delete(a *action) interface{} {
if len(b.objects) > 0 { if len(b.objects) > 0 {
fatalf(409, "Conflict", "The container you tried to delete is not empty") fatalf(409, "Conflict", "The container you tried to delete is not empty")
} }
delete(a.srv.Containers, b.name) delete(a.user.Containers, b.name)
a.user.Containers-- a.user.Account.Containers--
return nil return nil
} }
@ -316,8 +319,8 @@ func (r containerResource) put(a *action) interface{} {
}, },
} }
r.container.setMetadata(a, "container") r.container.setMetadata(a, "container")
a.srv.Containers[r.name] = r.container a.user.Containers[r.name] = r.container
a.user.Containers++ a.user.Account.Containers++
} }
return nil return nil
@ -430,7 +433,7 @@ func (objr objectResource) get(a *action) interface{} {
if manifest, ok := obj.meta["X-Object-Manifest"]; ok { if manifest, ok := obj.meta["X-Object-Manifest"]; ok {
var segments []io.Reader var segments []io.Reader
components := strings.SplitN(manifest[0], "/", 2) components := strings.SplitN(manifest[0], "/", 2)
segContainer := a.srv.Containers[components[0]] segContainer := a.user.Containers[components[0]]
prefix := components[1] prefix := components[1]
resp := segContainer.list("", "", prefix, "") resp := segContainer.list("", "", prefix, "")
sum := md5.New() sum := md5.New()
@ -575,7 +578,7 @@ func (objr objectResource) copy(a *action) interface{} {
objr2 objectResource objr2 objectResource
) )
destURL, _ := url.Parse("/v1/AUTH_tk/" + destination) destURL, _ := url.Parse("/v1/AUTH_" + TEST_ACCOUNT + "/" + destination)
r := a.srv.resourceForURL(destURL) r := a.srv.resourceForURL(destURL)
switch t := r.(type) { switch t := r.(type) {
case objectResource: case objectResource:
@ -665,16 +668,35 @@ func (s *SwiftServer) serveHTTP(w http.ResponseWriter, req *http.Request) {
panic(notAuthorized()) panic(notAuthorized())
} }
key := req.Header.Get("x-auth-token")
session, ok := s.Sessions[key[7:]]
if !ok {
panic(notAuthorized())
}
a.user = s.Accounts[session.username]
r = s.resourceForURL(req.URL) r = s.resourceForURL(req.URL)
key := req.Header.Get("x-auth-token")
if key == "" {
secretKey := ""
signature := req.URL.Query().Get("temp_url_sig")
expires := req.URL.Query().Get("temp_url_expires")
accountName, _, _, _ := s.parseURL(req.URL)
if account, ok := s.Accounts[accountName]; ok {
secretKey = account.meta.Get("X-Account-Meta-Temp-Url-Key")
}
mac := hmac.New(sha1.New, []byte(secretKey))
body := fmt.Sprintf("%s\n%s\n%s", req.Method, expires, req.URL.Path)
mac.Write([]byte(body))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
if signature != expectedSignature {
panic(notAuthorized())
}
} else {
session, ok := s.Sessions[key[7:]]
if !ok {
panic(notAuthorized())
}
a.user = s.Accounts[session.username]
}
switch req.Method { switch req.Method {
case "PUT": case "PUT":
resp = r.put(a) resp = r.put(a)
@ -712,22 +734,38 @@ func jsonMarshal(w io.Writer, x interface{}) {
} }
} }
var pathRegexp = regexp.MustCompile("/v1/AUTH_[a-zA-Z0-9]+(/([^/]+)(/(.*))?)?") var pathRegexp = regexp.MustCompile("/v1/AUTH_([a-zA-Z0-9]+)(/([^/]+)(/(.*))?)?")
func (srv *SwiftServer) parseURL(u *url.URL) (account string, container string, object string, err error) {
m := pathRegexp.FindStringSubmatch(u.Path)
if m == nil {
return "", "", "", fmt.Errorf("Couldn't parse the specified URI")
}
account = m[1]
container = m[3]
object = m[5]
return
}
// resourceForURL returns a resource object for the given URL. // resourceForURL returns a resource object for the given URL.
func (srv *SwiftServer) resourceForURL(u *url.URL) (r resource) { func (srv *SwiftServer) resourceForURL(u *url.URL) (r resource) {
m := pathRegexp.FindStringSubmatch(u.Path) accountName, containerName, objectName, err := srv.parseURL(u)
if m == nil {
fatalf(404, "InvalidURI", "Couldn't parse the specified URI") if err != nil {
fatalf(404, "InvalidURI", err.Error())
} }
containerName := m[2]
objectName := m[4] account, ok := srv.Accounts[accountName]
if !ok {
fatalf(404, "NoSuchAccount", "The specified account does not exist")
}
if containerName == "" { if containerName == "" {
return rootResource{} return rootResource{}
} }
b := containerResource{ b := containerResource{
name: containerName, name: containerName,
container: srv.Containers[containerName], container: account.Containers[containerName],
} }
if objectName == "" { if objectName == "" {
@ -780,7 +818,7 @@ func (rootResource) get(a *action) interface{} {
h := a.w.Header() h := a.w.Header()
h.Set("X-Account-Bytes-Used", strconv.Itoa(int(a.user.BytesUsed))) h.Set("X-Account-Bytes-Used", strconv.Itoa(int(a.user.BytesUsed)))
h.Set("X-Account-Container-Count", strconv.Itoa(int(a.user.Containers))) h.Set("X-Account-Container-Count", strconv.Itoa(int(a.user.Account.Containers)))
h.Set("X-Account-Object-Count", strconv.Itoa(int(a.user.Objects))) h.Set("X-Account-Object-Count", strconv.Itoa(int(a.user.Objects)))
// add metadata // add metadata
@ -792,7 +830,7 @@ func (rootResource) get(a *action) interface{} {
var tmp orderedContainers var tmp orderedContainers
// first get all matching objects and arrange them in alphabetical order. // first get all matching objects and arrange them in alphabetical order.
for _, container := range a.srv.Containers { for _, container := range a.user.Containers {
if strings.HasPrefix(container.name, prefix) { if strings.HasPrefix(container.name, prefix) {
tmp = append(tmp, container) tmp = append(tmp, container)
} }
@ -858,19 +896,19 @@ func NewSwiftServer(address string) (*SwiftServer, error) {
} }
server := &SwiftServer{ server := &SwiftServer{
Listener: l, Listener: l,
AuthURL: "http://" + l.Addr().String() + "/v1.0", AuthURL: "http://" + l.Addr().String() + "/v1.0",
URL: "http://" + l.Addr().String() + "/v1", URL: "http://" + l.Addr().String() + "/v1",
Containers: make(map[string]*container), Accounts: make(map[string]*account),
Accounts: make(map[string]*account), Sessions: make(map[string]*session),
Sessions: make(map[string]*session),
} }
server.Accounts["swifttest"] = &account{ server.Accounts[TEST_ACCOUNT] = &account{
password: "swifttest", password: TEST_ACCOUNT,
metadata: metadata{ metadata: metadata{
meta: make(http.Header), meta: make(http.Header),
}, },
Containers: make(map[string]*container),
} }
go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {

View file

@ -778,6 +778,17 @@ This storage backend uses Openstack Swift object storage.
Your Openstack domain id for Identity v3 API. Your Openstack domain id for Identity v3 API.
</td> </td>
</tr> </tr>
<tr>
<td>
<code>trustid</code>
</td>
<td>
no
</td>
<td>
Your Openstack trust id for Identity v3 API.
</td>
</tr>
<tr> <tr>
<td> <td>
<code>insecureskipverify</code> <code>insecureskipverify</code>

View file

@ -61,6 +61,7 @@ type Parameters struct {
TenantID string TenantID string
Domain string Domain string
DomainID string DomainID string
TrustID string
Region string Region string
Container string Container string
Prefix string Prefix string
@ -156,6 +157,7 @@ func New(params Parameters) (*Driver, error) {
TenantId: params.TenantID, TenantId: params.TenantID,
Domain: params.Domain, Domain: params.Domain,
DomainId: params.DomainID, DomainId: params.DomainID,
TrustId: params.TrustID,
Transport: transport, Transport: transport,
ConnectTimeout: 60 * time.Second, ConnectTimeout: 60 * time.Second,
Timeout: 15 * 60 * time.Second, Timeout: 15 * 60 * time.Second,

View file

@ -29,6 +29,7 @@ func init() {
tenantID string tenantID string
domain string domain string
domainID string domainID string
trustID string
container string container string
region string region string
insecureSkipVerify bool insecureSkipVerify bool
@ -42,6 +43,7 @@ func init() {
tenantID = os.Getenv("SWIFT_TENANT_ID") tenantID = os.Getenv("SWIFT_TENANT_ID")
domain = os.Getenv("SWIFT_DOMAIN_NAME") domain = os.Getenv("SWIFT_DOMAIN_NAME")
domainID = os.Getenv("SWIFT_DOMAIN_ID") domainID = os.Getenv("SWIFT_DOMAIN_ID")
trustID = os.Getenv("SWIFT_TRUST_ID")
container = os.Getenv("SWIFT_CONTAINER_NAME") container = os.Getenv("SWIFT_CONTAINER_NAME")
region = os.Getenv("SWIFT_REGION_NAME") region = os.Getenv("SWIFT_REGION_NAME")
insecureSkipVerify, _ = strconv.ParseBool(os.Getenv("SWIFT_INSECURESKIPVERIFY")) insecureSkipVerify, _ = strconv.ParseBool(os.Getenv("SWIFT_INSECURESKIPVERIFY"))
@ -71,6 +73,7 @@ func init() {
tenantID, tenantID,
domain, domain,
domainID, domainID,
trustID,
region, region,
container, container,
root, root,