-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial implemetation of the askpass logic * wip: makefile updates * found the missing bit * local askpass img in the reconciler mgr * linting issues * cloud metadata library addition * pr feedback Co-authored-by: Mike Borozdin <[email protected]>
- Loading branch information
Showing
8 changed files
with
290 additions
and
3 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
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,102 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// 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 main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"net/http" | ||
|
||
"cloud.google.com/go/compute/metadata" | ||
"k8s.io/klog/v2/klogr" | ||
"kpt.dev/configsync/pkg/askpass" | ||
"kpt.dev/configsync/pkg/util" | ||
utillog "kpt.dev/configsync/pkg/util/log" | ||
) | ||
|
||
// all the flags and their usage. | ||
var flHelp = flag.Bool("help", | ||
false, | ||
"print help and usage information") | ||
|
||
var flPort = flag.Int("port", | ||
util.EnvInt("ASKPASS_PORT", 9102), | ||
"port to listen on") | ||
|
||
var flGsaEmail = flag.String("email", | ||
util.EnvString("GSA_EMAIL", ""), | ||
"Google Service Account for authentication") | ||
|
||
var flErrorFile = flag.String("error-file", | ||
util.EnvString("ASKPASS_ERROR_FILE", ""), | ||
"the name of a file into which errors will be written defaults to \"\", disabling error reporting") | ||
|
||
var flRoot = flag.String("root", | ||
util.EnvString("ASKPASS_ROOT", util.EnvString("HOME", "")+"/askpass"), | ||
"the root directory for askpass") | ||
|
||
// main function is designed only to deal with the environment. i.e. parse | ||
// user input and make sure we can set up a server. All the logic outside | ||
// of OS and network interractions should be in the package with the logic | ||
// for askpass | ||
func main() { | ||
// if people are looking for help we are not going to launch anything | ||
// assuming that they didn't mean to start the askpass process. | ||
if *flHelp { | ||
flag.Usage() | ||
return | ||
} | ||
|
||
utillog.Setup() | ||
log := utillog.NewLogger(klogr.New(), *flRoot, *flErrorFile) | ||
|
||
log.Info("starting askpass with arguments", "--port", *flPort, | ||
"--email", *flGsaEmail, "--error-file", *flErrorFile, "--root", *flRoot) | ||
|
||
if *flPort == 0 { | ||
utillog.HandleError(log, true, | ||
"ERROR: port can not be zero") | ||
} | ||
|
||
if *flRoot == "" { | ||
utillog.HandleError(log, true, "root cannot be empty") | ||
} | ||
|
||
var gsaEmail string | ||
var err error | ||
// for getting the GSA email we have several scenarios | ||
// the first one is that the user provides it to us. | ||
// the second scenario is that it's not provided but we can get the | ||
// Compute Engine default service account from the metadata server. | ||
if *flGsaEmail != "" { | ||
gsaEmail = *flGsaEmail | ||
} else if metadata.OnGCE() { | ||
gsaEmail, err = metadata.Email("") | ||
if err != nil { | ||
utillog.HandleError(log, false, "error in http.ListenAndServe: %v", err) | ||
} | ||
} else { | ||
utillog.HandleError(log, true, | ||
"ERROR: GSA email can not be empty") | ||
} | ||
|
||
aps := &askpass.Server{ | ||
Email: gsaEmail, | ||
} | ||
http.HandleFunc("/git_askpass", aps.GitAskPassHandler) | ||
|
||
if err := http.ListenAndServe(fmt.Sprintf(":%d", *flPort), nil); err != nil { | ||
utillog.HandleError(log, false, "error in http.ListenAndServe: %v", err) | ||
} | ||
} |
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,93 @@ | ||
// Copyright 2023 Google LLC | ||
// | ||
// 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 askpass is designed to be used in the askpass sidecar | ||
// to provide GSA authentication services. | ||
package askpass | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
"k8s.io/klog/v2" | ||
) | ||
|
||
// Server contains server wide state and settings for the askpass sidecar | ||
type Server struct { | ||
Email string | ||
token *oauth2.Token | ||
} | ||
|
||
// GitAskPassHandler is the main method for clients to ask us for | ||
// credentials | ||
func (aps *Server) GitAskPassHandler(w http.ResponseWriter, r *http.Request) { | ||
klog.Infof("handling new askpass request from host: %s", r.Host) | ||
|
||
if aps.needNewToken() { | ||
err := aps.retrieveNewToken(r.Context()) | ||
if err != nil { | ||
klog.Error(err) | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
} else { | ||
klog.Infof("reusing existing oauth2 token, type: %s, expiration: %v", | ||
aps.token.TokenType, aps.token.Expiry) | ||
} | ||
|
||
// this this point we should be equipped with all the credentials | ||
// and it's just a matter of sending it back to the caller. | ||
password := aps.token.AccessToken | ||
w.WriteHeader(http.StatusOK) | ||
if _, err := fmt.Fprintf(w, "username=%s\npassword=%s", aps.Email, password); err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
klog.Error(err) | ||
} | ||
} | ||
|
||
// needNewToken will tell us if we have an Oauth2 token that is | ||
// has not expired yet. | ||
func (aps *Server) needNewToken() bool { | ||
if aps.token == nil { | ||
return true | ||
} | ||
|
||
if time.Now().After(aps.token.Expiry) { | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
|
||
// retrieveNewToken will use the default credentials in order to | ||
// fetch to fetch a new token. Note the side effect that the | ||
// server token will be replaced in case of a successful retrieval. | ||
func (aps *Server) retrieveNewToken(ctx context.Context) error { | ||
creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/cloud-platform") | ||
if err != nil { | ||
return fmt.Errorf("error calling google.FindDefaultCredentials: %w", err) | ||
} | ||
aps.token, err = creds.TokenSource.Token() | ||
if err != nil { | ||
return fmt.Errorf("error retrieveing TokenSource.Token: %w", err) | ||
} | ||
|
||
klog.Infof("retrieved new Oauth2 token, type: %s, expiration: %v", | ||
aps.token.TokenType, aps.token.Expiry) | ||
return nil | ||
} |
Oops, something went wrong.