-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: reworked git-auth; added github-app support
close #141 Signed-off-by: Christian Kotzbauer <[email protected]>
- Loading branch information
1 parent
4b9256f
commit eb12f65
Showing
14 changed files
with
301 additions
and
77 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 |
---|---|---|
|
@@ -25,4 +25,4 @@ dist | |
*~ | ||
|
||
work | ||
auth | ||
/auth |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package auth | ||
|
||
import "github.com/go-git/go-git/v5/plumbing/transport" | ||
|
||
type GitAuthenticator interface { | ||
IsAvailable() bool | ||
ResolveAuth() (transport.AuthMethod, 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package auth | ||
|
||
import ( | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/json" | ||
"encoding/pem" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strings" | ||
"time" | ||
|
||
"github.com/go-git/go-git/v5/plumbing/transport" | ||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http" | ||
"github.com/golang-jwt/jwt" | ||
) | ||
|
||
var ( | ||
githubURL = "https://api.github.com/app/installations" | ||
) | ||
|
||
type gitHubToken struct { | ||
Token string `json:"token"` | ||
ExpiresAt time.Time `json:"expires_at"` | ||
} | ||
|
||
type GitHubAuthenticator struct { | ||
AppID string | ||
AppInstallationID string | ||
PrivateKey string | ||
currentToken *gitHubToken | ||
} | ||
|
||
func (g *GitHubAuthenticator) IsAvailable() bool { | ||
return g.AppID != "" && g.AppInstallationID != "" && g.PrivateKey != "" | ||
} | ||
|
||
func (g *GitHubAuthenticator) ResolveAuth() (transport.AuthMethod, error) { | ||
if g.currentToken == nil || g.currentToken.ExpiresAt.Before(time.Now().Add(1*time.Minute)) { | ||
token, err := getGitHubToken(g.PrivateKey, g.AppID, g.AppInstallationID) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
g.currentToken = token | ||
} | ||
|
||
return &githttp.BasicAuth{Username: "<token>", Password: g.currentToken.Token}, nil | ||
} | ||
|
||
func issueJWTFromPEM(key *rsa.PrivateKey, appID string) (string, error) { | ||
claims := &jwt.StandardClaims{ | ||
IssuedAt: time.Now().Add(-1 * time.Minute).Unix(), | ||
ExpiresAt: time.Now().Add(time.Minute * 5).Unix(), | ||
Issuer: appID, | ||
} | ||
|
||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) | ||
ss, err := token.SignedString(key) | ||
if err != nil { | ||
return "", fmt.Errorf("cannot retrieve signed string: %w", err) | ||
} | ||
|
||
return ss, nil | ||
} | ||
|
||
func getInstallationToken(jwtToken, appInstallationID string) (*gitHubToken, error) { | ||
url := strings.Join([]string{githubURL, appInstallationID, "access_tokens"}, "/") | ||
responseBody := gitHubToken{} | ||
client := &http.Client{} | ||
req, err := http.NewRequest("POST", url, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("cannot create new request: %w", err) | ||
} | ||
|
||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwtToken)) | ||
req.Header.Set("Accept", "application/vnd.github.v3+json") | ||
res, err := client.Do(req) | ||
if err != nil { | ||
return nil, fmt.Errorf("request execution failed: %w", err) | ||
} | ||
|
||
b, err := io.ReadAll(res.Body) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read response body: %w", err) | ||
} | ||
|
||
if res.StatusCode < 200 || res.StatusCode > 300 { | ||
return nil, fmt.Errorf("unexpected http-status %v: %s", res.StatusCode, string(b)) | ||
} | ||
|
||
if err := json.Unmarshal(b, &responseBody); err != nil { | ||
return nil, fmt.Errorf("cannot unmarshal response: %w", err) | ||
} | ||
|
||
return &responseBody, nil | ||
} | ||
|
||
func loadPEMFromBytes(key []byte) (*rsa.PrivateKey, error) { | ||
b, _ := pem.Decode(key) | ||
if b != nil { | ||
key = b.Bytes | ||
} | ||
|
||
parsedKey, err := x509.ParsePKCS1PrivateKey(key) | ||
if err != nil { | ||
return nil, fmt.Errorf("cannot parse private key: %w", err) | ||
} | ||
|
||
return parsedKey, nil | ||
} | ||
|
||
func getGitHubToken(privateKey, appID, appInstallationID string) (*gitHubToken, error) { | ||
pemBytes, err := base64.StdEncoding.DecodeString(privateKey) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to decode private-key from base64: %w", err) | ||
} | ||
|
||
key, err := loadPEMFromBytes(pemBytes) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to load PEM: %w", err) | ||
} | ||
|
||
jwtToken, err := issueJWTFromPEM(key, appID) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed issue a jwt from PEM: %w", err) | ||
} | ||
|
||
token, err := getInstallationToken(jwtToken, appInstallationID) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to get installation-token: %w", err) | ||
} | ||
|
||
return token, 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,20 @@ | ||
package auth | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestGithub(t *testing.T) { | ||
privateKey := "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBM3Q0L2dRN2ZoQkFsaVlod1BRTUpwNFh5K3Nwc2VtU2JOd1J5R01HL1B4THUrUGZGCkVaZTQ0dVdOZ2NtaWJGMVNFeDUvY2ZoZXFnL1R6YjlVUkhyLzQ3MjEyL3NvZlJLVUtPbmJ5TnhaQ1pJUUNVK1EKRDdxTkVWVC93NmlGbjkxNjFyRTNFWk1Rb0JkbDdUdTZXZXlWeWE4bU45S0tXWjQvTDR2aktlbFZrNzRrUnFMUApOdG1SalhsVmpBbWZML0piTzRMTmVvWndrRlpFcWE1eXBqM0cyckFoQ1BJcS9RK0MwT1hwYk5XRWVzKzNmV1BtCkVZOHN1dGpiTjlKbnpxc1ByZjFwdkFUY0RrU3EzMlFDalJuVjBMZTFKbVRhNUV6YkFZdmYxWVdIQjBVR1VaQWIKT1Bta3JLOEJ2QVRYY0FkRFpES21qS0VuVkdkQ292c2VlZFlRVlFJREFRQUJBb0lCQVFEUlhocHNaSXVJSmg4TgpjQlo0eXFURU11c0x2U1JoYXFnQjdIUS9XNndOVmYwWCtMT3lqa1VMS3JsZ0p3c21JQmtkaXZZUkhGSGFFMXBYCm5CbE81L29DZWRNaG9IWHQ0WWs4VWsyK0NYcS9xZTdVd0dERzFlcjJVRktoL1d2aXZnOUxKMXBqY1RRYUlVRUkKdDZhZUdjcXhtSkFmdXZOcnMyVUtockRXUzdTd1FCdnllVTdpOG50WUx0MTg2TXAvWjc2NWI0UVFCNXRqUlZRSwpaZHhadGxLVUc3a2tWdlNWdFdZcXNYWkwzV2RUK0tBQy85RUFQRzYrMHRlY0lkODVhUTgwREZManhlZUowdWk1CmJCYlVveFI2U1JpUnBDYnhZRWNWTU5wWitrMW9jME1NMndkZFYrOUxzc1BmSDFYeDNLKzYzUzJOYVcrWGZqOVYKRnNNU2ZleEJBb0dCQVBoOHBWTVRnYjBYVnRUaU9ISEJyQ2NKc0ZEWGNyWCtaZGV0NEErZWJiREExUHo5bmlDcQpOSmovOGgrN0R6RnNWcFJSWXU4NzF0ZUwzakpZOERIMEJKWGtVcXorRXNkWU95MkNFMDQyTFJZUHBEanNpWVpwCmNwT2pTYmRQMUYyMWtldDdoRW4vSmdwZUlSdTRoY0pKbW1iUDV6TTA0OTBFRDJ2UjVGN0pKcThsQW9HQkFPV2IKVG5NTEZWZFZnV2wxUlRnZjMyNjB6T3ZaeHlLbnluK25qVFhKenh5MDFad0pvYUpwSWxkYUYxa0ZsUGFTTnF6QwpoK1RFanRHU042Y1E3cTNQSTdzZWNxVW9NSHJUZmJGc0ZOS3d3Wk5ISjZnYmN5ODczWUg0V1RpS01salFURExqCjIrUHp5V212RTBxTldSMXZzamMvZGs1YXJ2SG1xNm1VWnNNTGNHMXhBb0dCQUp6SnlXZEovNnJZZHltTU5DRlQKR3RUbkFDR0NVQmNWbGgwbzBTQUp6eHlnRDhReUl2eHBhUmJCS0gxdEFvRWs0dHhqSGhIbHFBSUpkd0k5ckJGWQo3eXZGRFlXcUt4OHRST25LRVhSZ0tzS295KzQvRWVlRjBZM21ralRnWWhkZTRkdTc3QlRrc1pRSklNcmsrMEZFClAvQnZibXFaRE00QUtkcldiVXFCa2E4aEFvR0FTU1ljYlJ4WDFUWUJmNmFEd2VpdGx5aDByS0w2QlRsdGt4Ty8KTWlhMEhCSWtoNHl0K1A4RG1NNno1NEdXdnFOQVZmTzN0Wk5GYU5EcmVBYzE4eHV0NTN4QXpBc3N2c1RJSTVFcQpaU0U0Mm9IdHlXbk5YWHYyQ3VVakQ1Yy9pUG43Zks4SksrdEwvS1p4UCt3ZU92bU56bm9pWjc0T1JEVFg5Yk80CnRXVGNrWkVDZ1lBdDl0dDhneHlvVFpvQjl6d3RpakcrTFAxM3JteEg4d3c1VGZMODV3U2hDMElpZ3FITVJJT2gKeURHNkFiMnJ0cVNNNWN2M05TcGFFd2UxSzhjSVYzMkx2M3EyTzVjdUswNkc2MDVMMFVweDlZZ2pCVjByZkR0SQpqY2QvOSs4QkF3alB1TVgxRmZTTXNXNTFkdjdjRVd0bkNSaGNQZlphRFU1RExzWDNqNUtYWGc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=" | ||
appID := "236826" | ||
appInstallationID := "29089394" | ||
|
||
a := GitHubAuthenticator{PrivateKey: privateKey, AppID: appID, AppInstallationID: appInstallationID} | ||
gitAuth, err := a.ResolveAuth() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
fmt.Println(gitAuth.String()) | ||
} |
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,21 @@ | ||
package auth | ||
|
||
import ( | ||
"github.com/go-git/go-git/v5/plumbing/transport" | ||
"github.com/go-git/go-git/v5/plumbing/transport/http" | ||
) | ||
|
||
type TokenGitAuthenticator struct { | ||
Token string | ||
} | ||
|
||
func (t *TokenGitAuthenticator) IsAvailable() bool { | ||
return t.Token != "" | ||
} | ||
|
||
func (t *TokenGitAuthenticator) ResolveAuth() (transport.AuthMethod, error) { | ||
return &http.BasicAuth{ | ||
Username: "<token>", // this can be anything except an empty string | ||
Password: t.Token, | ||
}, nil | ||
} |
Oops, something went wrong.