github.com/argoproj/argo-cd/v3@v3.2.1/util/askpass/server.go (about) 1 package askpass 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "os" 8 "sync" 9 10 "github.com/google/uuid" 11 "google.golang.org/grpc" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/status" 14 15 "github.com/argoproj/argo-cd/v3/util/git" 16 utilio "github.com/argoproj/argo-cd/v3/util/io" 17 ) 18 19 type Server interface { 20 git.CredsStore 21 AskPassServiceServer 22 Run(path string) error 23 } 24 25 // server is a gRPC server that provides a way for an external process (usually git) to access credentials without those 26 // credentials being set directly in the git process's environment. Before invoking git, the caller invokes Add to add a 27 // new credential, which returns a unique id. The caller then sets the GIT_ASKPASS environment variable to the path of 28 // the argocd-git-ask-pass binary and sets the ASKPASS_NONCE environment variable to the id. When git needs credentials, 29 // it will invoke the argocd-git-ask-pass binary, which will use the ASKPASS_NONCE to look up the credentials and return 30 // them to git. After the git process completes, the caller should invoke Remove to remove the credential. 31 // 32 // This is meant to solve a class of problems that was demonstrated by an old bug in Kustomize. We needed to enable 33 // Kustomize to invoke git to fetch a private repository. But Kustomize had a bug that allowed a user to dump the 34 // environment variables of the process into manifests, which would expose the credentials. Kustomize eventually fixed 35 // the bug. But to prevent this from happening again, we now only set the ASKPASS_NONCE environment variable instead of 36 // directly passing the git credentials via environment variables. Even if the nonce leaks, 1) the user probably doesn't 37 // have access to the server to look up the corresponding git credentials, and 2) the nonce should be deleted from 38 // the server before the user even sees the manifests. 39 type server struct { 40 lock sync.Mutex 41 creds map[string]Creds 42 socketPath string 43 } 44 45 // NewServer returns a new server 46 func NewServer(socketPath string) *server { 47 return &server{ 48 creds: make(map[string]Creds), 49 socketPath: socketPath, 50 } 51 } 52 53 func (s *server) GetCredentials(_ context.Context, q *CredentialsRequest) (*CredentialsResponse, error) { 54 if q.Nonce == "" { 55 return nil, status.Errorf(codes.InvalidArgument, "missing nonce") 56 } 57 creds, ok := s.getCreds(q.Nonce) 58 if !ok { 59 return nil, status.Errorf(codes.NotFound, "unknown nonce") 60 } 61 return &CredentialsResponse{Username: creds.Username, Password: creds.Password}, nil 62 } 63 64 func (s *server) Start(path string) (utilio.Closer, error) { 65 _ = os.Remove(path) 66 listener, err := net.Listen("unix", path) 67 if err != nil { 68 return nil, err 69 } 70 server := grpc.NewServer() 71 RegisterAskPassServiceServer(server, s) 72 go func() { 73 _ = server.Serve(listener) 74 }() 75 return utilio.NewCloser(listener.Close), nil 76 } 77 78 func (s *server) Run() error { 79 _, err := s.Start(s.socketPath) 80 return err 81 } 82 83 // Add adds a new credential to the server and returns associated id 84 func (s *server) Add(username string, password string) string { 85 s.lock.Lock() 86 defer s.lock.Unlock() 87 id := uuid.New().String() 88 s.creds[id] = Creds{ 89 Username: username, 90 Password: password, 91 } 92 return id 93 } 94 95 // Remove removes the credential with the given id 96 func (s *server) Remove(id string) { 97 s.lock.Lock() 98 defer s.lock.Unlock() 99 delete(s.creds, id) 100 } 101 102 func (s *server) getCreds(id string) (*Creds, bool) { 103 s.lock.Lock() 104 defer s.lock.Unlock() 105 creds, ok := s.creds[id] 106 return &creds, ok 107 } 108 109 // Environ returns the environment variables that should be set when invoking git. 110 func (s *server) Environ(id string) []string { 111 return []string{ 112 "GIT_ASKPASS=argocd", 113 fmt.Sprintf("%s=%s", ASKPASS_NONCE_ENV, id), 114 "GIT_TERMINAL_PROMPT=0", 115 "ARGOCD_BINARY_NAME=argocd-git-ask-pass", 116 fmt.Sprintf("%s=%s", AKSPASS_SOCKET_PATH_ENV, s.socketPath), 117 } 118 }