Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@aliculPix4D
Copy link
Contributor

@aliculPix4D aliculPix4D commented May 5, 2025

validate github app

This is an attempt to address: #162 (comment)

and parse the private pem key during validation period.

@marco-m-pix4d attempt to implement what we were discussing..

ClientId string `json:"client_id"`
InstallationId int `json:"installation_id"`
PrivateKey string `json:"private_key"` // SENSITIVE
parsedRSAKey *rsa.PrivateKey
Copy link
Contributor Author

@aliculPix4D aliculPix4D May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like the fact that this is nil unless someone calls app.Validate() method first.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is normal life with pointers.
The real question is: why do we need to use a pointer here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping

Copy link
Contributor Author

@aliculPix4D aliculPix4D May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not super valid reasons but:

  1. it seemed as a good "fit" because I could just do:
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(app.PrivateKey))
...
app.parsedRSAKey = key

since jwt.ParseRSAPrivateKeyFromPEM returns a pointer.
2. it allow me to easily check for empty struct with: *app == GitHubApp{}
Now, I had to write a more "complex" function to do that.

Anyway... none of this is a good enough explanation and I am happy to hear your opinion on when to use one or the other approach...

}

if len(mandatory) > 0 {
return fmt.Errorf("github_app: missing mandatory keys: %s", strings.Join(mandatory, ", "))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we first validate that all 3 fields are configured and then we parse the private RSA key...

},
wantErr: "source: missing keys: github_app.client_id",
},
{
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is now better tested in: github/githubapp_test.go...

Comment on lines 265 to 266
// if access token is not set; we are using GitHub app. Validate it.
if src.AccessToken == "" {
Copy link
Contributor

@marco-m-pix4d marco-m-pix4d May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this way of testing confusing and even error-prone if we change the order of the code. It seems to me that the previous way (deleted line 266) was more straightforward. What was the reason to change the logic for the if ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At that point, we know that src.AccessToken != "" xor src.GitHubApp != github.GitHubApp{} is true so I agree with Marco that doing the corresponding direct test would be better than the contrapositive.

Suggested change
// if access token is not set; we are using GitHub app. Validate it.
if src.AccessToken == "" {
// if access token is not set; we are using GitHub app. Validate it.
if src.GitHubApp != github.GitHubApp{} {

Copy link
Contributor Author

@aliculPix4D aliculPix4D May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done as suggested. I keep using the IsZero() method..

We can't compare with: src.GitHubApp != github.GitHubApp{} anymore since I added the rsa.Private key field that is not a pointer.

Comment on lines 66 to 72
privateKey, err := testhelp.GeneratePrivateKey(t, 2048)
assert.NilError(t, err)

app := github.GitHubApp{
ClientId: "client-id",
InstallationId: 12345,
PrivateKey: string(testhelp.EncodePrivateKeyToPEM(privateKey)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am looking at lines 66 and 72. In both cases we own the functions that manipulate the key data (they are 2 test helpers).

What is not clear to me is this: Why are we doing 2 transformations? Why not using only 1 test helper, that produces directly a private key encoded to PEM ?

Copy link
Contributor Author

@aliculPix4D aliculPix4D May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah the reason for the split was I need both the *rsa.PrivateKey and PEM string...

e.g. in DecodeJWT(... key *rsa.PrivateKey)) function I have to use: &key.PublicKey

Sadly, I remembered this point only after I implemented the fix according to your suggestion. Hence, I had to revert to what I had, with minor change... EncodePrivateKeyToPEM now returns a string instead of slice of bytes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub UI is killing me. Going back to the thread in question:

I still do not understand. Where is privateKey used in the test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is privateKey used in the test?

You mean where is:

privateKey, err := testhelp.GeneratePrivateKey(t, 2048)

used?

Its used here:

app := github.GitHubApp{
		...
		PrivateKey:     string(testhelp.EncodePrivateKeyToPEM(privateKey)),

where I pass it as a parameter to function: testhelp.EncodePrivateKeyToPEM

Or I don't understand your question....

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it seems that my original question is still valid:

What is not clear to me is this: Why are we doing 2 transformations? Why not using only 1 test helper, that produces directly a private key encoded to PEM ?

?

Copy link
Contributor Author

@aliculPix4D aliculPix4D May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I answered this already just above...

At first, I fell in the same trap and went to adjust according to your comment... Only to found the real reason: There are more tests... In some other tests, I need a public part of the private key in some third function: DecodeJWT(... key *rsa.PrivateKey)) where I have to use: &key.PublicKey

So:

  1. I create a private key with:
    privateKey, err := testhelp.GeneratePrivateKey(t, 2048)
  2. In some tests its enough to get the PEM string: testhelp.EncodePrivateKeyToPEM
  3. in other tests I need both the PEM string and public key part of the private key OBJECT (&key.PublicKey)... In example, in some tests I want to decode the JWT token to confirm it contains correct/expected claims...

Comment on lines 265 to 266
// if access token is not set; we are using GitHub app. Validate it.
if src.AccessToken == "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At that point, we know that src.AccessToken != "" xor src.GitHubApp != github.GitHubApp{} is true so I agree with Marco that doing the corresponding direct test would be better than the contrapositive.

Suggested change
// if access token is not set; we are using GitHub app. Validate it.
if src.AccessToken == "" {
// if access token is not set; we are using GitHub app. Validate it.
if src.GitHubApp != github.GitHubApp{} {

key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(app.PrivateKey))
if err != nil {
return "", fmt.Errorf("could not parse private key: %w", err)
return fmt.Errorf("github_app: could not parse private key: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the error you get from jwt tell you it should be in PEM format? If not, that would be worthwhile to mention it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be the full error (see the test below):

github_app: could not parse private key: invalid key: Key must be a PEM encoded PKCS1 or PKCS8 ke

@aliculPix4D aliculPix4D force-pushed the pci-4263-github-app-validation branch from f5f010f to ce472c4 Compare May 5, 2025 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants