Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GitHub app authentication #878

Merged

Conversation

risset
Copy link
Contributor

@risset risset commented May 19, 2024

Related issue: #877

Currently the e2e::github_app_auth test should work locally, if the correct env vars are set. I've tested the feature that way before creating the PR. Ideally it would be possible to run it in the GHA workflow. One way I thought that might be feasible is if an app was created and installed to a particular (private) repo, and then its private key was stored as a git-sync repository secret.

Some information about GitHub app authentication: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app

Copy link

linux-foundation-easycla bot commented May 19, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

  • ✅ login: risset / name: Liam Wyllie (5822b73)
  • ✅ login: nan-yu / name: Nan Yu (ba2fe67)

@k8s-ci-robot
Copy link
Contributor

Welcome @risset!

It looks like this is your first PR to kubernetes/git-sync 🎉. Please refer to our pull request process documentation to help your PR have a smooth ride to approval.

You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. Here is the bot commands documentation.

You can also check if kubernetes/git-sync has its own contribution guidelines.

You may want to refer to our testing guide if you run into trouble with your tests not passing.

If you are having difficulty getting your pull request seen, please follow the recommended escalation practices. Also, for tips and tricks in the contribution process you may want to read the Kubernetes contributor cheat sheet. We want to make sure your contribution gets all the attention it needs!

Thank you, and welcome to Kubernetes. 😃

@k8s-ci-robot k8s-ci-robot added cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. and removed cncf-cla: no Indicates the PR's author has not signed the CNCF CLA. labels May 19, 2024
Copy link
Member

@thockin thockin left a comment

Choose a reason for hiding this comment

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

I'll have to think more about this when I get a second. It looks pretty clean overall.

Also need to add flags to the manual (at the bottom of the file).

I'll think about how to make e2e tests optional.

_ = resp.Body.Close()
}()
if resp.StatusCode != 201 {
errMessage, err := io.ReadAll(resp.Body)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we bound the read here to 1K or 4K or 32K or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would make sense, should the same apply to the CallAskPassURL usage of io.ReadAll?

Copy link
Member

Choose a reason for hiding this comment

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

Missed this before, yeah, it should :)

Copy link
Member

Choose a reason for hiding this comment

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

You know what, this is fine. I'm just being paranoid. Doing a bounded copy is nice, but I won't require it here.

main.go Outdated
@@ -1851,6 +1878,72 @@ func (git *repoSync) CallAskPassURL(ctx context.Context) error {
return nil
}

func (git *repoSync) RefreshAppToken(ctx context.Context, githubBaseURL, privateKey string, appID, installationID int) error {
Copy link
Member

@thockin thockin May 20, 2024

Choose a reason for hiding this comment

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

I'd like the entire set of function names to reflect "GitHubApp" rather than just app, so it's easier to read. Could also do with a nice comment, please

main.go Outdated
@@ -254,6 +257,19 @@ func main() {
envString("", "GITSYNC_ASKPASS_URL", "GIT_SYNC_ASKPASS_URL", "GIT_ASKPASS_URL"),
"a URL to query for git credentials (username=<value> and password=<value>)")

flGithubAPIURL := pflag.String("github-api-url",
Copy link
Member

Choose a reason for hiding this comment

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

it's not really a URL is it? Usually a URL includes "scheme" or "protocol", but later you prepend "https://". Also - is this allowed to have paths? E.g. could I say "notgithub.com/mytenant/path" ?

I propose to either make this a URL ("https://host:port/path") or rename it to "host" or "domain".

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah you're right that

I think having it as an actual base URL is better, I'll make this change

main.go Outdated
@@ -254,6 +257,19 @@ func main() {
envString("", "GITSYNC_ASKPASS_URL", "GIT_SYNC_ASKPASS_URL", "GIT_ASKPASS_URL"),
"a URL to query for git credentials (username=<value> and password=<value>)")

flGithubAPIURL := pflag.String("github-api-url",
envString("api.github.com", "GITHUB_API_URL"),
Copy link
Member

Choose a reason for hiding this comment

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

Is this env name special (e.g. used somewhere deep in a library)? I don't think so, right?

All the othe env vars are named "GITSYNC_xyz", so these should probably be "GITSYNC_GITHUB_APP_xyz"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

main.go Outdated
envString("", "GITHUB_APP_PRIVATE_KEY"),
"the GitHub app private key to use for GitHub app auth")
flGithubAppApplicationID := pflag.Int("github-app-application-id",
envInt(-1, "GITHUB_APP_APPLICATION_ID"),
Copy link
Member

Choose a reason for hiding this comment

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

why not 0 for default? Is that a valid value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't 100% sure that it couldn't be a valid value, but now you mention it, it doesn't seem likely that the IDs would start at 0 anyway. I'll have them default to 0

main.go Outdated
flGithubAPIURL := pflag.String("github-api-url",
envString("api.github.com", "GITHUB_API_URL"),
"the API URL to use when making requests to GitHub when using app auth")
flGithubAppPrivateKey := pflag.String("github-app-private-key",
Copy link
Member

Choose a reason for hiding this comment

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

We need to validate these (see ~ L493). Specifically:

  • You can't use these and some other form of auth at the same time
  • If user sets this flag, they also need to set the app ID and installation ID

Copy link
Member

Choose a reason for hiding this comment

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

Actually, we should not put private keys as flags, they are listable via /proc. The --password flag is bad for the same reason.

We should have a way to declare a non-flag which can be set by env but not via CLI. I will think about that. It's going to be a busy few weeks, so please hang tight.

Also, we should be able to pass this in via a file, like --password-file. For all of these, I'd like it to be possible to use a kubernetes secret and mount a volume, rather than flags/env vars. I'll have to refresh myself on formatting, i think we can only demand one value per file. So maybe we want an alternative --github-app-config=<dir> with documented files within it, e.g. api-url (optional), private-key (required), application-id (required), and installation-id (required). That way a user could make a single k8s Secret object with those key, and use a Secret-type volume.

Copy link
Contributor Author

@risset risset May 21, 2024

Choose a reason for hiding this comment

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

It's going to be a busy few weeks, so please hang tight.

No worries! thanks for taking the time for the detailed review, I will address the simple things you brought up in the meantime

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for using a single k8s Secret object with these keys and add validation for them. It would be much easier for the downstream service to provide these values.

main.go Outdated
@@ -789,6 +806,16 @@ func main() {
}
metricAskpassCount.WithLabelValues(metricKeySuccess).Inc()
}

if *flGithubAppPrivateKey != "" && *flGithubAppInstallationID != -1 && *flGithubAppApplicationID != -1 {
if git.appTokenExpiry.Before(time.Now()) {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't there be some buffer here, so we don't fail TOCTOU ? E.g. require max(20% of the token's lifespan, 30s) or something?

I don't want tokens to be valid here but fail to sync 1 second later, and some of the ops we do can take O(seconds).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense

main.go Outdated
if *flGithubAppPrivateKey != "" && *flGithubAppInstallationID != -1 && *flGithubAppApplicationID != -1 {
if git.appTokenExpiry.Before(time.Now()) {
if err := git.RefreshAppToken(ctx, *flGithubAPIURL, *flGithubAppPrivateKey, *flGithubAppApplicationID, *flGithubAppInstallationID); err != nil {
metricAskpassCount.WithLabelValues(metricKeyError).Inc()
Copy link
Member

Choose a reason for hiding this comment

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

I think we should have a new metric for this

Copy link
Member

Choose a reason for hiding this comment

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

This should be a different metric - it's not askpass

test_e2e.sh Outdated
##############################################
# Test github app auth
##############################################
function e2e::github_app_auth() {
Copy link
Member

Choose a reason for hiding this comment

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

Put this with the other e2e::auth_* tests and name it e2e::auth_github_app.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@thockin thockin self-assigned this May 20, 2024
@risset risset force-pushed the add-support-for-github-app-authentication branch from 7e947b5 to 834bc50 Compare May 24, 2024 15:19
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: risset
Once this PR has been reviewed and has the lgtm label, please ask for approval from thockin. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

if *flGithubAppInstallationID != 0 {
handleConfigError(log, true, "ERROR: --github-app-installation-id may only be specified when --github-app-private-key-file is specified")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This makes the Github app auth mutually exclusive with username/password. What about other auth types, like ssh, cookiefile, or etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I omitted those because I didn't see them being checked in the existing validations, but I can add them sure

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is a mess. Was OK when it was 3 mutually exclusive modes, but it's getting out of hand. OK for now, but we should plan a followup which is more declarative feeling and handles the mutual exclusion more automatically.

main.go Outdated
return err
}

url, err := url.JoinPath(githubBaseURL, fmt.Sprintf("installations/%d/access_tokens", installationID))
Copy link
Contributor

Choose a reason for hiding this comment

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

The path should be app/installations/%d/access_tokens.

main.go Outdated
@@ -254,6 +257,19 @@ func main() {
envString("", "GITSYNC_ASKPASS_URL", "GIT_SYNC_ASKPASS_URL", "GIT_ASKPASS_URL"),
"a URL to query for git credentials (username=<value> and password=<value>)")

flGithubBaseURL := pflag.String("github-base-url",
envString("https://api.github.com/", "GITSYNC_GITHUB_BASE_URL", "GIT_SYNC_GITHUB_BASE_URL"),
Copy link
Contributor

Choose a reason for hiding this comment

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

These flags are new, so you don't need the alternative keys with the GIT_SYNC prefix for backward compatibility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, addressed this

## Step 2: Export the necessary environment variables

The following environment variables are *required* to run the git-sync github app auth test:
- `GITHUB_APP_PRIVATE_KEY`
Copy link
Contributor

Choose a reason for hiding this comment

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

These env variables need to have the GITSYNC_ prefix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These ones don't correspond to the actual application env vars though, they're the values that get passed in via flags to the GIT_SYNC function in the test

Should have been saved when creating the app

### GITHUB_APP_APPLICATION_ID
The value after "App ID" in the app's settings page
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest adding the page URL: https://github.com/settings/apps/<unique_app_name>

@nan-yu
Copy link
Contributor

nan-yu commented Jun 4, 2024

While setting up a Github app, I noticed instead of using App ID to get installation tokens, it also supports using the Client ID.

I'm wondering have you experimented with the client ID?

@risset risset force-pushed the add-support-for-github-app-authentication branch from 834bc50 to 05e4771 Compare June 9, 2024 16:52
@risset
Copy link
Contributor Author

risset commented Jun 9, 2024

While setting up a Github app, I noticed instead of using App ID to get installation tokens, it also supports using the Client ID.

I'm wondering have you experimented with the client ID?

I didn't know about this, thanks. It appears to be a recent feature: https://github.blog/changelog/2024-05-01-github-apps-can-now-use-the-client-id-to-fetch-installation-tokens/

So the App ID and Client ID can be used interchangeably in this case.

The application ID is not being deprecated at this time, nor are their plans to remove it. However, compatibility with future features will rely on use of the client ID, so updating is recommended.

Should we have both the app ID and client ID as parameters and accept either of them, or just replace app ID with client ID?

@risset risset force-pushed the add-support-for-github-app-authentication branch from 261b10d to 10f7d70 Compare June 10, 2024 16:12
@nan-yu
Copy link
Contributor

nan-yu commented Jun 10, 2024

Should we have both the app ID and client ID as parameters and accept either of them, or just replace app ID with client ID?

I would vote for supporting both scenarios.

main.go Show resolved Hide resolved
@nan-yu
Copy link
Contributor

nan-yu commented Jun 25, 2024

Hi @risset , any recent update on addressing the comments in this PR?

I'm working on Config Sync. If possible, we would like to leverage this feature in our next minor release in July.

@thockin
Copy link
Member

thockin commented Jun 25, 2024 via email

@risset
Copy link
Contributor Author

risset commented Jun 25, 2024

Hi @risset , any recent update on addressing the comments in this PR?

Hi, I will be working on this some more over the next few days! I've been having some issues with running test_e2e.sh on my current (macOS) machine, but I'm able to test my changes on another Linux machine at least.

@risset risset force-pushed the add-support-for-github-app-authentication branch from 10f7d70 to 8aab47c Compare June 26, 2024 23:33
@nan-yu
Copy link
Contributor

nan-yu commented Jul 1, 2024

Hi Liam Wyllie , any recent update on addressing the comments in this PR?

Hi, I will be working on this some more over the next few days! I've been having some issues with running test_e2e.sh on my current (macOS) machine, but I'm able to test my changes on another Linux machine at least.

Thanks for the update. Did you run the e2e test with a public repo or a private repo?
I followed the instruction with a private repo, and git-sync returned a repo not found error. Not sure if that works for you.

@risset
Copy link
Contributor Author

risset commented Jul 3, 2024

Hi Liam Wyllie , any recent update on addressing the comments in this PR?

Hi, I will be working on this some more over the next few days! I've been having some issues with running test_e2e.sh on my current (macOS) machine, but I'm able to test my changes on another Linux machine at least.

Thanks for the update. Did you run the e2e test with a public repo or a private repo? I followed the instruction with a private repo, and git-sync returned a repo not found error. Not sure if that works for you.

I've been testing with a private repo. I'll double check the instructions and how I've set the app up etc. in case I missed something

@risset
Copy link
Contributor Author

risset commented Jul 8, 2024

Hi Liam Wyllie , any recent update on addressing the comments in this PR?

Hi, I will be working on this some more over the next few days! I've been having some issues with running test_e2e.sh on my current (macOS) machine, but I'm able to test my changes on another Linux machine at least.

Thanks for the update. Did you run the e2e test with a public repo or a private repo? I followed the instruction with a private repo, and git-sync returned a repo not found error. Not sure if that works for you.

I've been testing with a private repo. I'll double check the instructions and how I've set the app up etc. in case I missed something

Does the app you created definitely have read-only or read-write access for Contents under Repository permissions, and is installed to the private repository itself (only select repositories) under Install App?

When I re-run the tests with either of those not true, I also get the repo not found error.

@nan-yu
Copy link
Contributor

nan-yu commented Jul 8, 2024

Hi Liam Wyllie , any recent update on addressing the comments in this PR?

Hi, I will be working on this some more over the next few days! I've been having some issues with running test_e2e.sh on my current (macOS) machine, but I'm able to test my changes on another Linux machine at least.

Thanks for the update. Did you run the e2e test with a public repo or a private repo? I followed the instruction with a private repo, and git-sync returned a repo not found error. Not sure if that works for you.

I've been testing with a private repo. I'll double check the instructions and how I've set the app up etc. in case I missed something

Does the app you created definitely have read-only or read-write access for Contents under Repository permissions, and is installed to the private repository itself (only select repositories) under Install App?

When I re-run the tests with either of those not true, I also get the repo not found error.

No worries. I think it was the issue with my testing account. I created a robot Github account. The account is flagged and requires reinstatement.

It works with my personal account.

@risset
Copy link
Contributor Author

risset commented Jul 11, 2024

No worries. I think it was the issue with my testing account. I created a robot Github account. The account is flagged and requires reinstatement.

It works with my personal account.

Nice! Hopefully we can get this over the line. I'm still not quite sure how getting the tests to run properly via actions will work.

@nan-yu
Copy link
Contributor

nan-yu commented Jul 15, 2024

Regarding the e2e test for the github app integration, I created a robot GitHub account, a GitHub app, along with a private repository under the account. After that, I set up the required environment variables in GitHub Secret in my personal fork. All e2e test passed with changes in risset#1: https://github.com/nan-yu/git-sync/actions/runs/9947634472.

Can we use the robot account for testing, @thockin ?

@k8s-ci-robot k8s-ci-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 17, 2024
@risset risset force-pushed the add-support-for-github-app-authentication branch from 8aab47c to d7ecc0b Compare July 18, 2024 08:11
@k8s-ci-robot k8s-ci-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 20, 2024
Copy link
Member

@thockin thockin left a comment

Choose a reason for hiding this comment

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

Can you also update the README? Basically just delete the last section, build the binary and run it with --man >> README.md, then add the last triple-ticks.

Other than my own automated and manual testing, it looks good.

main.go Outdated
envString("", "GITSYNC_GITHUB_APP_PRIVATE_KEY_FILE"),
"the file from which the private key for GitHub app auth will be sourced")
flGithubAppClientID := pflag.String("github-app-client-id",
envString("", "GTSYNC_GITHUB_APP_CLIENT_ID"),
Copy link
Member

Choose a reason for hiding this comment

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

Typo: GTSYNC should be GITSYNC.

Same on the next one, too

main.go Show resolved Hide resolved
if *flGithubAppInstallationID != 0 {
handleConfigError(log, true, "ERROR: --github-app-installation-id may only be specified when --github-app-private-key-file is specified")
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is a mess. Was OK when it was 3 mutually exclusive modes, but it's getting out of hand. OK for now, but we should plan a followup which is more declarative feeling and handles the mutual exclusion more automatically.

main.go Outdated
if *flGithubAppPrivateKey != "" && *flGithubAppInstallationID != -1 && *flGithubAppApplicationID != -1 {
if git.appTokenExpiry.Before(time.Now()) {
if err := git.RefreshAppToken(ctx, *flGithubAPIURL, *flGithubAppPrivateKey, *flGithubAppApplicationID, *flGithubAppInstallationID); err != nil {
metricAskpassCount.WithLabelValues(metricKeyError).Inc()
Copy link
Member

Choose a reason for hiding this comment

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

This should be a different metric - it's not askpass

_ = resp.Body.Close()
}()
if resp.StatusCode != 201 {
errMessage, err := io.ReadAll(resp.Body)
Copy link
Member

Choose a reason for hiding this comment

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

Missed this before, yeah, it should :)

main.go Outdated Show resolved Hide resolved
@risset risset force-pushed the add-support-for-github-app-authentication branch from d7ecc0b to 3ea1801 Compare July 27, 2024 17:33
@risset
Copy link
Contributor Author

risset commented Jul 27, 2024

Have updated to address recent feedback !

Pardon my ignorance here but I'm still a bit unsure on how best to address your comment on the usage of io.ReadAll to read the HTTP response body. Is reading with io.Copy to a bytes.Buffer preferable here, or is there a more efficient approach to that?

Also I've added one, but not sure if you feel a metric is actually needed for this feature, it was just sloppiness from me having the askpass code fragment left in there before ...

Copy link
Contributor

@nan-yu nan-yu left a comment

Choose a reason for hiding this comment

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

It looks mostly good to me. I would like to enable the e2e tests for this new feature.
Feel free to port over changes in https://github.com/risset/git-sync/pull/1/files.

main.go Outdated
@@ -2492,6 +2665,22 @@ AUTHENTICATION
When --cookie-file ($GITSYNC_COOKIE_FILE) is specified, the
associated cookies can contain authentication information.

github app
When --github-app-private-key-file ($GITSYNC_GITHUB_APP_PRIVATE_KEY_FILE),
--github-app-application-id ($GITSYNC_GITHUB_APP_APPLICATION_ID)
Copy link
Contributor

@nan-yu nan-yu Jul 27, 2024

Choose a reason for hiding this comment

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

--github-app-application-id or --github-app-client-id

test_e2e.sh Outdated
@@ -295,6 +295,7 @@ function GIT_SYNC() {
-v "$DOT_SSH/1/id_test":"/ssh/secret.1":ro \
-v "$DOT_SSH/2/id_test":"/ssh/secret.2":ro \
-v "$DOT_SSH/3/id_test":"/ssh/secret.3":ro \
-v "$(pwd)/$GITHUB_APP_PRIVATE_KEY_FILE":"/github_app_private_key.pem":ro \
Copy link
Contributor

@nan-yu nan-yu Jul 27, 2024

Choose a reason for hiding this comment

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

This requires the github app private key file to be located at the same directory. It won't work with the e2e test in the GitHub Actions Workflow.

I'm not sure if you get a chance to take a look at risset#1. Basically, it loads the private key from GitHub Secrets and stored it in a temp file. It would be good to incorporate it in this PR and enables the e2e 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.

I will take a look at that

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. I updated risset#1 to make the github app auth test skippable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Merged this in now

Copy link
Member

@thockin thockin left a comment

Choose a reason for hiding this comment

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

I am AFK for a couple weeks, and don't want to merge until I can run some.manual tests, but it looks great

main.go Outdated

metricRefreshGitHubAppTokenCount = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "git_sync_refresh_github_app_token_count",
Help: "How many times github app token was refreshed, partitioned by state (success, error)",
Copy link
Member

Choose a reason for hiding this comment

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

Super nit: "the GitHub app token"

main.go Outdated
@@ -838,6 +896,17 @@ func main() {
}
metricAskpassCount.WithLabelValues(metricKeySuccess).Inc()
}

if *flGithubAppPrivateKeyFile != "" && *flGithubAppInstallationID != 0 && (*flGithubAppApplicationID != 0 || *flGithubAppClientID != "") {
Copy link
Member

Choose a reason for hiding this comment

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

Should this be considering the newly added environment also?

main.go Outdated
func (git *repoSync) RefreshGitHubAppToken(ctx context.Context, githubBaseURL, privateKeyFile, clientID string, appID, installationID int) error {
git.log.V(3).Info("refreshing GitHub app token")

privateKeyBytes, err := os.ReadFile(privateKeyFile)
Copy link
Member

Choose a reason for hiding this comment

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

Should this consider the environment var key?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Forgot about this, added it now

_ = resp.Body.Close()
}()
if resp.StatusCode != 201 {
errMessage, err := io.ReadAll(resp.Body)
Copy link
Member

Choose a reason for hiding this comment

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

You know what, this is fine. I'm just being paranoid. Doing a bounded copy is nice, but I won't require it here.

main.go Outdated

--github-app-application-id <int>, $GITSYNC_GITHUB_APP_APPLICATION_ID
The app ID of the GitHub app used for GitHub app authentication.
One of --github-app-application-id or --github-app-client-id is required.
Copy link
Member

Choose a reason for hiding this comment

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

...when GitHub app authentication is used.

Same below

@risset risset force-pushed the add-support-for-github-app-authentication branch from 10d816d to 12d712e Compare July 31, 2024 20:02
risset and others added 2 commits July 31, 2024 21:08
The GitHub app e2e test requires a GitHub app to be created and
installed, and also requires a few environment variables to be set.

This commit updates the GitHub action workflow by providing the
environment variables which can be set via GitHub Secret. GitHub
Secrests cannot start with `GITHUB_`. Hence, this commit prepends
`TEST_` to the env variables.

It also updates how GitHub app private key file is set. It can be set by
either `TEST_GITHUB_APP_PRIVATE_KEY` or
`TEST_GITHUB_APP_PRIVATE_KEY_FILE`.
@risset risset force-pushed the add-support-for-github-app-authentication branch from 12d712e to ba2fe67 Compare July 31, 2024 20:08
# Set the trap to call the final_cleanup function on exit.
trap final_cleanup EXIT

skip_github_app_test="${SKIP_GITHUB_APP_TEST:-false}"
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think skipping may be a more user friendly default, since in most cases a developer won't have github app credentials ready to use

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes sense as long as the github action workflow doesn't skip the github app test.

skip_github_app_test="${SKIP_GITHUB_APP_TEST:-false}"
required_env_vars=()
LOCAL_GITHUB_APP_PRIVATE_KEY_FILE="github_app_private_key.pem"
GITHUB_APP_PRIVATE_KEY_MOUNT=""
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be declared as an empty array so that it is properly expanded below when the volume mount is not added.

e.g.

GITHUB_APP_PRIVATE_KEY_MOUNT=()
...
  # Mount the GitHub App private key file to the git-sync container
  GITHUB_APP_PRIVATE_KEY_MOUNT+=(-v "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}:/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}:ro")
...
        "${GITHUB_APP_PRIVATE_KEY_MOUNT[@]}" \
        "${GIT_SYNC_E2E_IMAGE}" \

Copy link
Contributor

Choose a reason for hiding this comment

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

Changing it to an empty array looks good to me. Thanks for pointing it out.

@sdowell
Copy link
Contributor

sdowell commented Sep 5, 2024

@risset @thockin Is this PR still being worked on?

@risset
Copy link
Contributor Author

risset commented Sep 5, 2024

@risset @thockin Is this PR still being worked on?

Yeah I was just waiting for @thockin as he said he'd be away for a while. Also I think your review comments make sense but since @nan-yu wrote those parts of test_e2e.sh, I thought they might have some thoughts on it before I change those things. @nan-yu what do you think?

@thockin
Copy link
Member

thockin commented Sep 5, 2024

Yes, the ball is in my court to evaluate testing. I apologize, it has been a chaotic few weeks since I got back from holiday. I have not forgotten it.

if [[ ! -v TEST_GITHUB_APP_PRIVATE_KEY_FILE || -z "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}" ]]; then
TEST_GITHUB_APP_PRIVATE_KEY_FILE="${DIR}/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}"
fi
echo "${TEST_GITHUB_APP_PRIVATE_KEY}" > "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}"
Copy link
Member

Choose a reason for hiding this comment

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

I'm worried about this path, seems like an opportunity to blow away a file we shouldn't. Maybe we can make a temp file and use that?

Copy link
Contributor

Choose a reason for hiding this comment

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

TEST_GITHUB_APP_PRIVATE_KEY_FILE points to a temp file when it is either unset or blank. Another option is to always use the temp file regardless of whether the variable is set, i.e., remove the if condition in line 222.

if [[ -v TEST_GITHUB_APP_PRIVATE_KEY && -n "${TEST_GITHUB_APP_PRIVATE_KEY}" ]]; then
  TEST_GITHUB_APP_PRIVATE_KEY_FILE="${DIR}/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}"
  echo "${TEST_GITHUB_APP_PRIVATE_KEY}" > "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}"
fi

Copy link
Member

Choose a reason for hiding this comment

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

I'm making a few edits as I look at this, will post them here. May not finish today.

Copy link
Member

Choose a reason for hiding this comment

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

OK, I dug into the testing in depth and set up my own app. With the below diff I am happy with this PR, but I have not yet looked at making CI work (with GH secrets)

diff --git a/test_e2e.sh b/test_e2e.sh
index 9b2023f..142d7c1 100755
--- a/test_e2e.sh
+++ b/test_e2e.sh
@@ -41,6 +41,11 @@ function fail() {
     return 42
 }
 
+function skip() {
+    echo "SKIP" >&3
+    return 43
+}
+
 function pass() {
     echo "PASS"
 }
@@ -204,37 +209,43 @@ function final_cleanup() {
 # Set the trap to call the final_cleanup function on exit.
 trap final_cleanup EXIT
 
-skip_github_app_test="${SKIP_GITHUB_APP_TEST:-false}"
+skip_github_app_test="${SKIP_GITHUB_APP_TEST:-true}"
 required_env_vars=()
 LOCAL_GITHUB_APP_PRIVATE_KEY_FILE="github_app_private_key.pem"
-GITHUB_APP_PRIVATE_KEY_MOUNT=""
+GITHUB_APP_PRIVATE_KEY_MOUNT=()
 if [[ "${skip_github_app_test}" != "true" ]]; then
   required_env_vars=(
     "TEST_GITHUB_APP_AUTH_TEST_REPO"
     "TEST_GITHUB_APP_APPLICATION_ID"
     "TEST_GITHUB_APP_INSTALLATION_ID"
     "TEST_GITHUB_APP_CLIENT_ID"
-    "TEST_GITHUB_APP_PRIVATE_KEY_FILE"
   )
 
-  # TEST_GITHUB_APP_PRIVATE_KEY, if set, overrides TEST_GITHUB_APP_PRIVATE_KEY_FILE
-  if [[ -v TEST_GITHUB_APP_PRIVATE_KEY && -n "${TEST_GITHUB_APP_PRIVATE_KEY}" ]]; then
-    if [[ ! -v TEST_GITHUB_APP_PRIVATE_KEY_FILE || -z "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}" ]]; then
-      TEST_GITHUB_APP_PRIVATE_KEY_FILE="${DIR}/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}"
-    fi
-    echo "${TEST_GITHUB_APP_PRIVATE_KEY}" > "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}"
+  if [[ -n "${TEST_GITHUB_APP_PRIVATE_KEY_FILE:-}" && -n "${TEST_GITHUB_APP_PRIVATE_KEY:-}" ]]; then
+      echo "ERROR: Both TEST_GITHUB_APP_PRIVATE_KEY_FILE and TEST_GITHUB_APP_PRIVATE_KEY were specified."
+      exit 1
+  fi
+  if [[ -n "${TEST_GITHUB_APP_PRIVATE_KEY_FILE:-}" ]]; then
+    cp "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}" "${DIR}/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}"
+  elif [[ -n "${TEST_GITHUB_APP_PRIVATE_KEY:-}" ]]; then
+    echo "${TEST_GITHUB_APP_PRIVATE_KEY}" > "${DIR}/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}"
+  else
+    echo "ERROR: Neither TEST_GITHUB_APP_PRIVATE_KEY_FILE nor TEST_GITHUB_APP_PRIVATE_KEY was specified."
+    echo "       Either provide a value or skip this test (SKIP_GITHUB_APP_TEST=true)."
+    exit 1
   fi
 
   # Validate all required environment variables for the github-app-auth tests are provided.
   for var in "${required_env_vars[@]}"; do
     if [[ ! -v "${var}" ]]; then
-      echo "Error: Required environment variable '${var}' is not set or empty. Either provide a value or skip the GitHub App test by setting SKIP_GITHUB_APP_TEST to 'true'."
+      echo "ERROR: Required environment variable '${var}' is not set."
+      echo "       Either provide a value or skip this test (SKIP_GITHUB_APP_TEST=true)."
       exit 1
     fi
   done
 
   # Mount the GitHub App private key file to the git-sync container
-  GITHUB_APP_PRIVATE_KEY_MOUNT=(-v "${TEST_GITHUB_APP_PRIVATE_KEY_FILE}":"/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}":ro)
+  GITHUB_APP_PRIVATE_KEY_MOUNT=(-v "${DIR}/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}":"/${LOCAL_GITHUB_APP_PRIVATE_KEY_FILE}":ro)
 fi
 
 # WORK is temp space and in reset for each testcase.
@@ -2232,7 +2243,7 @@ function e2e::auth_askpass_url_slow_start() {
 ##############################################
 function e2e::auth_github_app_application_id() {
     if [[ "${skip_github_app_test}" == "true" ]]; then
-        return
+        skip
     fi
     GIT_SYNC \
         --one-time \
@@ -2247,7 +2258,7 @@ function e2e::auth_github_app_application_id() {
 
 function e2e::auth_github_app_client_id() {
     if [[ "${skip_github_app_test}" == "true" ]]; then
-        return
+        skip
     fi
     GIT_SYNC \
         --one-time \
@@ -3658,6 +3669,8 @@ for t; do
         run_test RUN_RET "${TEST_FN}" >"${LOG}.${RUN}" 2>&1
         if [[ "$RUN_RET" == 0 ]]; then
             pass
+        elif [[ "$RUN_RET" == 43 ]]; then
+            true # do nothing
         else
             TEST_RET=1
             if [[ "$RUN_RET" != 42 ]]; then

@thockin
Copy link
Member

thockin commented Sep 6, 2024

Since this is a kubernetes repo, I'll have to see about making the kubernetes org own the app

@HorusTheSonOfOsiris
Copy link

Since this is a kubernetes repo, I'll have to see about making the kubernetes org own the app

Any plans on releasing this soon? This could help our team a lot.

@thockin
Copy link
Member

thockin commented Sep 25, 2024

I'm going to merge this and followup. I Still don't have CI for GH apps up, though.

@thockin thockin merged commit 8441240 into kubernetes:master Sep 25, 2024
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants