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  }