-
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.
- Loading branch information
0 parents
commit a57aaf4
Showing
5 changed files
with
237 additions
and
0 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 |
---|---|---|
@@ -0,0 +1 @@ | ||
iap_curl |
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,29 @@ | ||
iap_curl | ||
======== | ||
|
||
curl wrapper for making HTTP request to IAP-protected app in CLI more easier than curl | ||
|
||
## Usage | ||
|
||
```console | ||
$ export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json" | ||
$ export IAP_CLIENT_ID="342624545358-asdfd8fas9df8sd7ga0sdguadfpvqp69.apps.googleusercontent.com" | ||
$ | ||
$ iap_curl http://iap-protected.webapp.com | ||
``` | ||
|
||
The option of iap_curl is fully compatible with curl one. | ||
|
||
## Installation | ||
|
||
``` | ||
$ go get github.com/b4b4r07/iap_curl | ||
``` | ||
|
||
## License | ||
|
||
MIT | ||
|
||
## Author | ||
|
||
b4b4r07 |
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,28 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
"os/exec" | ||
"runtime" | ||
) | ||
|
||
func doCurl(args []string) error { | ||
// Check if you have curl command | ||
command := "curl" | ||
if _, err := exec.LookPath(command); err != nil { | ||
return err | ||
} | ||
for _, arg := range args { | ||
command += " " + arg | ||
} | ||
var cmd *exec.Cmd | ||
if runtime.GOOS == "windows" { | ||
cmd = exec.Command("cmd", "/c", command) | ||
} else { | ||
cmd = exec.Command("sh", "-c", command) | ||
} | ||
cmd.Stderr = os.Stderr | ||
cmd.Stdout = os.Stdout | ||
cmd.Stdin = os.Stdin | ||
return cmd.Run() | ||
} |
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,114 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/json" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net/url" | ||
"time" | ||
|
||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
"golang.org/x/oauth2/jws" | ||
) | ||
|
||
func readRsaPrivateKey(bytes []byte) (*rsa.PrivateKey, error) { | ||
block, _ := pem.Decode(bytes) | ||
if block == nil { | ||
return nil, errors.New("invalid private key data") | ||
} | ||
|
||
var key *rsa.PrivateKey | ||
var err error | ||
if block.Type == "RSA PRIVATE KEY" { | ||
key, err = x509.ParsePKCS1PrivateKey(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} else if block.Type == "PRIVATE KEY" { | ||
keyInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var ok bool | ||
key, ok = keyInterface.(*rsa.PrivateKey) | ||
if !ok { | ||
return nil, errors.New("not RSA private key") | ||
} | ||
} else { | ||
return nil, fmt.Errorf("invalid private key type : %s", block.Type) | ||
} | ||
|
||
key.Precompute() | ||
|
||
if err := key.Validate(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return key, nil | ||
} | ||
|
||
func getToken(saPath, clientID string) (token string, err error) { | ||
sa, err := ioutil.ReadFile(saPath) | ||
if err != nil { | ||
return | ||
} | ||
conf, err := google.JWTConfigFromJSON(sa) | ||
if err != nil { | ||
return | ||
} | ||
rsaKey, _ := readRsaPrivateKey(conf.PrivateKey) | ||
iat := time.Now() | ||
exp := iat.Add(time.Hour) | ||
jwt := &jws.ClaimSet{ | ||
Iss: conf.Email, | ||
Aud: TokenURI, | ||
Iat: iat.Unix(), | ||
Exp: exp.Unix(), | ||
PrivateClaims: map[string]interface{}{ | ||
"target_audience": clientID, | ||
}, | ||
} | ||
jwsHeader := &jws.Header{ | ||
Algorithm: "RS256", | ||
Typ: "JWT", | ||
} | ||
|
||
msg, err := jws.Encode(jwsHeader, jwt, rsaKey) | ||
if err != nil { | ||
return | ||
} | ||
|
||
v := url.Values{} | ||
v.Set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer") | ||
v.Set("assertion", msg) | ||
|
||
ctx := context.Background() | ||
hc := oauth2.NewClient(ctx, nil) | ||
resp, err := hc.PostForm(TokenURI, v) | ||
if err != nil { | ||
return | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
|
||
var tokenRes struct { | ||
AccessToken string `json:"access_token"` | ||
TokenType string `json:"token_type"` | ||
IDToken string `json:"id_token"` | ||
ExpiresIn int64 `json:"expires_in"` | ||
} | ||
|
||
if err := json.Unmarshal(body, &tokenRes); err != nil { | ||
return token, err | ||
} | ||
|
||
token = tokenRes.IDToken | ||
return | ||
} |
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,65 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
) | ||
|
||
const ( | ||
TokenURI = "https://www.googleapis.com/oauth2/v4/token" | ||
|
||
GoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS" | ||
IAPClientID = "IAP_CLIENT_ID" | ||
) | ||
|
||
var helpText string = `Usage: curl | ||
` | ||
|
||
func main() { | ||
os.Exit(run(os.Args[1:])) | ||
} | ||
|
||
func run(args []string) int { | ||
var ( | ||
creds = os.Getenv(GoogleApplicationCredentials) | ||
clientID = os.Getenv(IAPClientID) | ||
) | ||
|
||
if len(args) > 0 { | ||
if args[0] == "-h" || args[0] == "--help" { | ||
fmt.Fprint(os.Stderr, helpText) | ||
return 1 | ||
} | ||
} | ||
|
||
if creds == "" { | ||
fmt.Fprintf(os.Stderr, "Error: %s is missing", GoogleApplicationCredentials) | ||
return 1 | ||
} | ||
|
||
if clientID == "" { | ||
fmt.Fprintf(os.Stderr, "Error: %s is missing", IAPClientID) | ||
return 1 | ||
} | ||
|
||
token, err := getToken(creds, clientID) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Error: %v\n", err.Error()) | ||
return 1 | ||
} | ||
|
||
authHeader := fmt.Sprintf("'Authorization: Bearer %s'", token) | ||
curlArgs := append( | ||
// For IAP header | ||
[]string{"-H", authHeader}, | ||
// Original args | ||
args..., | ||
) | ||
|
||
if err := doCurl(curlArgs); err != nil { | ||
fmt.Fprintf(os.Stderr, "Error: %v\n", err.Error()) | ||
return 1 | ||
} | ||
|
||
return 0 | ||
} |