forked from IBM/portieris
/
oauth.go
137 lines (123 loc) · 4.64 KB
/
oauth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2018 Portieris Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package oauth
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/golang/glog"
)
// GetHTTPClient gets an http client to use for getting an oauth token
// Takes the following as input:
// customFile - Path to custom ca certificate
// Returns:
// *http.Client
func GetHTTPClient(customFile string) *http.Client {
rootCA, err := x509.SystemCertPool()
customCA, err := ioutil.ReadFile(customFile)
if err != nil {
if os.IsNotExist(err) {
glog.Infof("CA not provided at %s, will use default system pool", customFile)
} else {
glog.Fatalf("Could not read %s: %s", customFile, err)
}
} else {
rootCA.AppendCertsFromPEM(customCA)
}
client := &http.Client{
Timeout: 10 * time.Minute,
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
DisableKeepAlives: false,
MaxIdleConnsPerHost: 10,
TLSHandshakeTimeout: 5 * time.Second,
TLSClientConfig: &tls.Config{
// Avoid fallback by default to SSL protocols < TLS1.2
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
RootCAs: rootCA,
},
},
}
return client
}
// Request is a helper for getting an OAuth token from the Registry OAuth Service.
// Takes the following as input:
// token - Auth token being used for the request
// repo - Repo you are requesting access too e.g. bainsy88/busybox
// username - Username for the OAuth request, identifies the type of token being passed in. Valid usernames are token (for registry token), iambearer, iamapikey, bearer (UAA bearer (legacy)), iamrefresh
// writeAccessRequired - Whether or not you require write (push and delete) access as well as read (pull)
// service - The service you are retrieving the OAuth token for. Current services are either "notary" or "registry"
// hostname - Hostname of the registry you wish to call e.g. https://icr.io
// Returns:
// *auth.TokenResponse - Details of the type is here https://github.ibm.com/alchemy-registry/registry-types/tree/master/auth#type-tokenresponse
// Token is the element you will need to forward to the registry/notary as part of a Bearer Authorization Header
// error
func Request(token string, repo string, username string, writeAccessRequired bool, service string, hostname string) (*TokenResponse, error) {
var actions string
//If you want to verify if a the credential supplied has read and write access to the repo we ask oauth for pull,push and *
if writeAccessRequired {
actions = "pull,push,*"
} else {
actions = "pull"
}
client := GetHTTPClient("/etc/certs/ca.pem")
oauthEndpoint := "/oauth/token"
if strings.Contains(hostname, "docker.io") {
hostname = "https://auth.docker.io"
oauthEndpoint = "/token"
}
values := url.Values{
"service": []string{fmt.Sprintf("%s.%s", service, hostname)},
"grant_type": {"password"},
"client_id": {"testclient"},
"username": {username},
"password": {token},
"scope": {"repository:" + repo + ":" + actions},
}
glog.Infof("URL values: %v", values)
resp, err := client.PostForm(hostname+oauthEndpoint, values)
if err != nil {
glog.Errorf("Error sending request to registry-oauth: %v", err)
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// Unexpected, read body for more information and close. It is the upstream callers
// responsibility to close the response body if an error is not returned.
glog.Errorf("Received non-success status code %v", resp.StatusCode)
var body []byte
if resp.Body != nil {
body, _ = ioutil.ReadAll(resp.Body)
}
return nil, fmt.Errorf("Request to OAuth failed with status code: %v and body: %s", resp.StatusCode, body[:50])
}
tokenResponse := TokenResponse{}
bytes, err := ioutil.ReadAll(resp.Body)
err = json.Unmarshal(bytes, &tokenResponse)
if err != nil {
return nil, fmt.Errorf("Failed to unmarshall OAuth response: %s", err)
}
return &tokenResponse, nil
}