2021-03-09 22:59:54 +00:00
/ *
Copyright 2021 The Kubernetes Authors All rights reserved .
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 addons
import (
"bytes"
"context"
2023-03-06 22:15:28 +00:00
"fmt"
2021-03-09 22:59:54 +00:00
"os"
"os/exec"
2021-08-10 20:46:49 +00:00
"path"
2021-03-09 22:59:54 +00:00
"strconv"
2021-03-24 20:47:18 +00:00
"time"
2021-03-09 22:59:54 +00:00
2021-03-15 18:39:06 +00:00
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022-04-14 18:16:44 +00:00
"github.com/pkg/errors"
"golang.org/x/oauth2/google"
2021-03-19 06:12:05 +00:00
"k8s.io/klog/v2"
2021-03-09 22:59:54 +00:00
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/config"
2021-03-09 23:11:54 +00:00
"k8s.io/minikube/pkg/minikube/detect"
2021-03-09 22:59:54 +00:00
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/mustload"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/reason"
2021-03-15 18:39:06 +00:00
"k8s.io/minikube/pkg/minikube/service"
2021-03-09 22:59:54 +00:00
"k8s.io/minikube/pkg/minikube/style"
)
2021-03-09 23:20:02 +00:00
const (
credentialsPath = "/var/lib/minikube/google_application_credentials.json"
projectPath = "/var/lib/minikube/google_cloud_project"
2021-03-17 20:39:55 +00:00
secretName = "gcp-auth"
2021-05-25 20:43:25 +00:00
namespaceName = "gcp-auth"
2022-04-14 18:16:44 +00:00
// readPermission correlates to read-only file system permissions
readPermission = "0444"
2021-03-09 23:20:02 +00:00
)
2021-03-09 22:59:54 +00:00
// enableOrDisableGCPAuth enables or disables the gcp-auth addon depending on the val parameter
2022-04-14 18:16:44 +00:00
func enableOrDisableGCPAuth ( cfg * config . ClusterConfig , name , val string ) error {
2021-03-09 22:59:54 +00:00
enable , err := strconv . ParseBool ( val )
if err != nil {
return errors . Wrapf ( err , "parsing bool: %s" , name )
}
if enable {
return enableAddonGCPAuth ( cfg )
}
return disableAddonGCPAuth ( cfg )
}
func enableAddonGCPAuth ( cfg * config . ClusterConfig ) error {
// Grab command runner from running cluster
cc := mustload . Running ( cfg . Name )
r := cc . CP . Runner
// Grab credentials from where GCP would normally look
ctx := context . Background ( )
creds , err := google . FindDefaultCredentials ( ctx )
2021-09-13 21:05:30 +00:00
if err != nil {
2021-08-10 20:46:49 +00:00
if detect . IsCloudShell ( ) {
if c := os . Getenv ( "CLOUDSDK_CONFIG" ) ; c != "" {
2021-09-28 10:58:01 +00:00
f , err := os . ReadFile ( path . Join ( c , "application_default_credentials.json" ) )
2021-08-10 20:46:49 +00:00
if err == nil {
creds , _ = google . CredentialsFromJSON ( ctx , f )
}
}
} else {
exit . Message ( reason . InternalCredsNotFound , "Could not find any GCP credentials. Either run `gcloud auth application-default login` or set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the path of your credentials file." )
}
2021-03-09 22:59:54 +00:00
}
2022-11-25 01:55:44 +00:00
// Patch service accounts for all namespaces to include the image pull secret.
// The image registry pull secret is added to the namespaces in the webhook.
if err := patchServiceAccounts ( cfg ) ; err != nil {
2022-11-29 20:54:49 +00:00
return errors . Wrap ( err , "patching service accounts" )
2021-08-04 23:35:26 +00:00
}
// If the env var is explicitly set, even in GCE, then defer to the user and continue
2021-08-31 22:02:13 +00:00
if ! Force && detect . IsOnGCE ( ) && os . Getenv ( "GOOGLE_APPLICATION_CREDENTIALS" ) == "" {
2021-08-10 19:57:10 +00:00
out . WarningT ( "It seems that you are running in GCE, which means authentication should work without the GCP Auth addon. If you would still like to authenticate using a credentials file, use the --force flag." )
return nil
2021-08-04 23:35:26 +00:00
}
2021-09-13 21:05:30 +00:00
if creds . JSON == nil {
2022-11-25 01:55:44 +00:00
out . WarningT ( "You have authenticated with a service account that does not have an associated JSON file. The GCP Auth addon requires credentials with a JSON file in order to continue." )
2021-09-13 21:05:30 +00:00
return nil
}
2021-03-15 18:39:06 +00:00
// Actually copy the creds over
2022-04-14 18:16:44 +00:00
f := assets . NewMemoryAssetTarget ( creds . JSON , credentialsPath , readPermission )
2021-03-09 22:59:54 +00:00
2022-04-14 18:16:44 +00:00
if err := r . Copy ( f ) ; err != nil {
2021-03-09 22:59:54 +00:00
return err
}
2021-05-25 20:43:25 +00:00
// First check if the project env var is explicitly set
projectEnv := os . Getenv ( "GOOGLE_CLOUD_PROJECT" )
if projectEnv != "" {
2022-04-14 18:16:44 +00:00
f := assets . NewMemoryAssetTarget ( [ ] byte ( projectEnv ) , projectPath , readPermission )
2021-05-25 20:43:25 +00:00
return r . Copy ( f )
}
2022-04-14 18:41:15 +00:00
// We're currently assuming gcloud is installed and in the user's path
2021-05-25 20:43:25 +00:00
proj , err := exec . Command ( "gcloud" , "config" , "get-value" , "project" ) . Output ( )
if err == nil && len ( proj ) > 0 {
2022-04-14 18:16:44 +00:00
f := assets . NewMemoryAssetTarget ( bytes . TrimSpace ( proj ) , projectPath , readPermission )
2021-05-25 20:43:25 +00:00
return r . Copy ( f )
}
out . WarningT ( "Could not determine a Google Cloud project, which might be ok." )
out . Styled ( style . Tip , ` To set your Google Cloud project , run :
gcloud config set project < project name >
or set the GOOGLE_CLOUD_PROJECT environment variable . ` )
// Copy an empty file in to avoid errors about missing files
2022-04-14 18:16:44 +00:00
emptyFile := assets . NewMemoryAssetTarget ( [ ] byte { } , projectPath , readPermission )
2021-05-25 20:43:25 +00:00
return r . Copy ( emptyFile )
}
2022-11-25 01:55:44 +00:00
func patchServiceAccounts ( cc * config . ClusterConfig ) error {
client , err := service . K8s . GetCoreClient ( cc . Name )
if err != nil {
return err
2021-08-10 20:46:49 +00:00
}
2022-11-25 01:55:44 +00:00
namespaces , err := client . Namespaces ( ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
if err != nil {
return err
}
2021-08-31 22:02:13 +00:00
2022-11-25 01:55:44 +00:00
for _ , n := range namespaces . Items {
// Now patch the secret into all the service accounts we can find
serviceaccounts := client . ServiceAccounts ( n . Name )
salist , err := serviceaccounts . List ( context . TODO ( ) , metav1 . ListOptions { } )
2021-08-31 22:02:13 +00:00
if err != nil {
return err
}
2022-11-25 01:55:44 +00:00
// Let's make sure we at least find the default service account
for len ( salist . Items ) == 0 {
salist , err = serviceaccounts . List ( context . TODO ( ) , metav1 . ListOptions { } )
2021-09-16 22:18:52 +00:00
if err != nil {
return err
}
2022-11-25 01:55:44 +00:00
time . Sleep ( 1 * time . Second )
}
2021-03-24 20:47:18 +00:00
2022-11-25 01:55:44 +00:00
ips := corev1 . LocalObjectReference { Name : secretName }
for _ , sa := range salist . Items {
add := true
for _ , ps := range sa . ImagePullSecrets {
if ps . Name == secretName {
add = false
break
2021-03-24 20:47:18 +00:00
}
2021-03-23 21:41:04 +00:00
}
2022-11-25 01:55:44 +00:00
if add {
sa . ImagePullSecrets = append ( sa . ImagePullSecrets , ips )
_ , err := serviceaccounts . Update ( context . TODO ( ) , & sa , metav1 . UpdateOptions { } )
2021-03-24 20:47:18 +00:00
if err != nil {
return err
}
2021-03-23 21:41:04 +00:00
}
}
2021-03-15 18:39:06 +00:00
}
2021-05-25 20:43:25 +00:00
return nil
}
2021-03-15 18:39:06 +00:00
2021-05-25 20:43:25 +00:00
func refreshExistingPods ( cc * config . ClusterConfig ) error {
2023-03-06 22:15:28 +00:00
klog . Info ( "refreshing existing pods" )
2021-05-25 20:43:25 +00:00
client , err := service . K8s . GetCoreClient ( cc . Name )
if err != nil {
2023-03-06 22:15:28 +00:00
return fmt . Errorf ( "failed to get k8s client: %v" , err )
2021-03-09 22:59:54 +00:00
}
2021-05-25 20:43:25 +00:00
namespaces , err := client . Namespaces ( ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
if err != nil {
2023-03-06 22:15:28 +00:00
return fmt . Errorf ( "failed to get namespaces: %v" , err )
2021-03-09 22:59:54 +00:00
}
2021-05-25 20:43:25 +00:00
for _ , n := range namespaces . Items {
// Ignore kube-system and gcp-auth namespaces
2021-09-16 22:06:31 +00:00
if skipNamespace ( n . Name ) {
2021-05-25 20:43:25 +00:00
continue
}
2021-03-09 22:59:54 +00:00
2021-05-25 20:43:25 +00:00
pods := client . Pods ( n . Name )
podList , err := pods . List ( context . TODO ( ) , metav1 . ListOptions { } )
if err != nil {
2023-03-06 22:15:28 +00:00
return fmt . Errorf ( "failed to list pods: %v" , err )
2021-05-25 20:43:25 +00:00
}
2021-03-09 22:59:54 +00:00
2021-05-25 20:43:25 +00:00
for _ , p := range podList . Items {
// Skip pods we're explicitly told to skip
if _ , ok := p . Labels [ "gcp-auth-skip-secret" ] ; ok {
continue
}
2021-03-09 22:59:54 +00:00
2023-03-06 22:15:28 +00:00
klog . Infof ( "refreshing pod %q" , p . Name )
2021-05-25 20:43:25 +00:00
// Recreating the pod should pickup the necessary changes
err := pods . Delete ( context . TODO ( ) , p . Name , metav1 . DeleteOptions { } )
if err != nil {
2023-03-06 22:15:28 +00:00
return fmt . Errorf ( "failed to delete pod %q: %v" , p . Name , err )
2021-05-25 20:43:25 +00:00
}
2021-03-09 22:59:54 +00:00
2021-05-25 20:43:25 +00:00
p . ResourceVersion = ""
2023-03-06 22:15:28 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 15 * time . Second )
defer cancel ( )
2021-05-25 20:43:25 +00:00
for err == nil {
2023-03-06 22:15:28 +00:00
if ctx . Err ( ) == context . DeadlineExceeded {
return fmt . Errorf ( "pod %q failed to restart" , p . Name )
}
2021-05-25 20:43:25 +00:00
_ , err = pods . Get ( context . TODO ( ) , p . Name , metav1 . GetOptions { } )
2023-03-06 22:15:28 +00:00
time . Sleep ( time . Second )
2021-05-25 20:43:25 +00:00
}
2022-04-14 18:16:44 +00:00
if _ , err := pods . Create ( context . TODO ( ) , & p , metav1 . CreateOptions { } ) ; err != nil {
2023-03-06 22:15:28 +00:00
return fmt . Errorf ( "failed to create pod %q: %v" , p . Name , err )
2021-05-25 20:43:25 +00:00
}
}
}
return nil
2021-03-09 22:59:54 +00:00
}
func disableAddonGCPAuth ( cfg * config . ClusterConfig ) error {
// Grab command runner from running cluster
cc := mustload . Running ( cfg . Name )
r := cc . CP . Runner
// Clean up the files generated when enabling the addon
2022-04-14 18:16:44 +00:00
creds := assets . NewMemoryAssetTarget ( [ ] byte { } , credentialsPath , readPermission )
2021-03-09 22:59:54 +00:00
err := r . Remove ( creds )
if err != nil {
return err
}
2022-04-14 18:16:44 +00:00
project := assets . NewMemoryAssetTarget ( [ ] byte { } , projectPath , readPermission )
if err := r . Remove ( project ) ; err != nil {
2021-03-09 22:59:54 +00:00
return err
}
2021-03-17 20:39:55 +00:00
client , err := service . K8s . GetCoreClient ( cfg . Name )
if err != nil {
return err
}
2021-03-24 20:47:18 +00:00
namespaces , err := client . Namespaces ( ) . List ( context . TODO ( ) , metav1 . ListOptions { } )
2021-03-17 20:39:55 +00:00
if err != nil {
return err
}
// No need to check for an error here, if the secret doesn't exist, no harm done.
for _ , n := range namespaces . Items {
2021-09-16 22:06:31 +00:00
if skipNamespace ( n . Name ) {
2021-09-15 15:59:19 +00:00
continue
}
2021-03-17 20:39:55 +00:00
secrets := client . Secrets ( n . Name )
2022-04-14 18:16:44 +00:00
if err := secrets . Delete ( context . TODO ( ) , secretName , metav1 . DeleteOptions { } ) ; err != nil {
2021-03-19 06:12:05 +00:00
klog . Infof ( "error deleting secret: %v" , err )
}
2021-09-14 20:12:07 +00:00
serviceaccounts := client . ServiceAccounts ( n . Name )
salist , err := serviceaccounts . List ( context . TODO ( ) , metav1 . ListOptions { } )
if err != nil {
klog . Infof ( "error getting service accounts: %v" , err )
return err
}
for _ , sa := range salist . Items {
for i , ps := range sa . ImagePullSecrets {
if ps . Name == secretName {
sa . ImagePullSecrets = append ( sa . ImagePullSecrets [ : i ] , sa . ImagePullSecrets [ i + 1 : ] ... )
2022-04-14 18:16:44 +00:00
if _ , err := serviceaccounts . Update ( context . TODO ( ) , & sa , metav1 . UpdateOptions { } ) ; err != nil {
2021-09-14 20:12:07 +00:00
return err
}
break
}
}
}
2021-03-17 20:39:55 +00:00
}
2021-03-09 22:59:54 +00:00
return nil
}
2022-04-14 18:16:44 +00:00
func verifyGCPAuthAddon ( cc * config . ClusterConfig , name , val string ) error {
2021-03-09 22:59:54 +00:00
enable , err := strconv . ParseBool ( val )
if err != nil {
return errors . Wrapf ( err , "parsing bool: %s" , name )
}
2021-08-10 19:57:10 +00:00
// If we're in GCE and didn't actually start the gcp-auth pods, don't check for them.
2021-08-12 21:13:51 +00:00
// We also don't want to actually set the addon as enabled, so just exit completely.
2021-08-31 22:02:13 +00:00
if enable && ! Force && detect . IsOnGCE ( ) && os . Getenv ( "GOOGLE_APPLICATION_CREDENTIALS" ) == "" {
2021-08-13 20:42:02 +00:00
return ErrSkipThisAddon
2021-08-10 19:57:10 +00:00
}
2021-05-26 16:26:10 +00:00
if Refresh {
2022-04-14 18:16:44 +00:00
if err := refreshExistingPods ( cc ) ; err != nil {
2021-05-25 20:43:25 +00:00
return err
}
}
2021-03-09 22:59:54 +00:00
2022-06-07 00:46:44 +00:00
if err := verifyAddonStatusInternal ( cc , name , val , "gcp-auth" ) ; err != nil {
return err
}
2021-03-09 22:59:54 +00:00
if enable && err == nil {
out . Styled ( style . Notice , "Your GCP credentials will now be mounted into every pod created in the {{.name}} cluster." , out . V { "name" : cc . Name } )
out . Styled ( style . Notice , "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration." )
2021-05-26 16:33:33 +00:00
if ! Refresh {
2021-05-26 16:26:10 +00:00
out . Styled ( style . Notice , "If you want existing pods to be mounted with credentials, either recreate them or rerun addons enable with --refresh." )
2021-05-25 20:43:25 +00:00
}
2021-03-09 22:59:54 +00:00
}
return err
}
2021-09-16 22:06:31 +00:00
func skipNamespace ( name string ) bool {
return name == metav1 . NamespaceSystem || name == namespaceName
}