github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/common/credentialcommon/modelcredential.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package credentialcommon 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/errors" 9 "gopkg.in/juju/names.v2" 10 11 "github.com/juju/juju/apiserver/common" 12 "github.com/juju/juju/apiserver/params" 13 "github.com/juju/juju/caas" 14 "github.com/juju/juju/cloud" 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/context" 17 "github.com/juju/juju/state" 18 ) 19 20 // ValidateExistingModelCredential checks if the cloud credential that a given model uses is valid for it. 21 func ValidateExistingModelCredential(backend PersistentBackend, callCtx context.ProviderCallContext) (params.ErrorResults, error) { 22 model, err := backend.Model() 23 if err != nil { 24 return params.ErrorResults{}, errors.Trace(err) 25 } 26 27 credentialTag, isSet := model.CloudCredential() 28 if !isSet { 29 return params.ErrorResults{}, nil 30 } 31 32 storedCredential, err := backend.CloudCredential(credentialTag) 33 if err != nil { 34 return params.ErrorResults{}, errors.Trace(err) 35 } 36 37 if !storedCredential.IsValid() { 38 return params.ErrorResults{}, errors.NotValidf("credential %q", storedCredential.Name) 39 } 40 credential := cloud.NewCredential(cloud.AuthType(storedCredential.AuthType), storedCredential.Attributes) 41 return ValidateNewModelCredential(backend, callCtx, credentialTag, &credential) 42 } 43 44 // ValidateNewModelCredential checks if a new cloud credential could be valid for a given model. 45 func ValidateNewModelCredential(backend PersistentBackend, callCtx context.ProviderCallContext, credentialTag names.CloudCredentialTag, credential *cloud.Credential) (params.ErrorResults, error) { 46 openParams, err := buildOpenParams(backend, credentialTag, credential) 47 if err != nil { 48 return params.ErrorResults{}, errors.Trace(err) 49 } 50 model, err := backend.Model() 51 if err != nil { 52 return params.ErrorResults{}, errors.Trace(err) 53 } 54 switch model.Type() { 55 case state.ModelTypeCAAS: 56 return checkCAASModelCredential(openParams) 57 case state.ModelTypeIAAS: 58 return checkIAASModelCredential(openParams, backend, callCtx) 59 default: 60 return params.ErrorResults{}, errors.NotSupportedf("model type %q", model.Type()) 61 } 62 } 63 64 func checkCAASModelCredential(brokerParams environs.OpenParams) (params.ErrorResults, error) { 65 broker, err := newCAASBroker(brokerParams) 66 if err != nil { 67 return params.ErrorResults{}, errors.Trace(err) 68 } 69 _, err = broker.Namespaces() 70 if err != nil { 71 // If this call could not be made with provided credential, we know that the credential is invalid. 72 return params.ErrorResults{}, errors.Trace(err) 73 } 74 return params.ErrorResults{}, nil 75 } 76 77 func checkIAASModelCredential(openParams environs.OpenParams, backend PersistentBackend, callCtx context.ProviderCallContext) (params.ErrorResults, error) { 78 env, err := newEnv(openParams) 79 if err != nil { 80 return params.ErrorResults{}, errors.Trace(err) 81 } 82 // We only check persisted machines vs known cloud instances. 83 // In the future, this check may be extended to other cloud resources, 84 // entities and operation-level authorisations such as interfaces, 85 // ability to CRUD storage, etc. 86 return checkMachineInstances(backend, env, callCtx) 87 } 88 89 // checkMachineInstances compares model machines from state with 90 // the ones reported by the provider using supplied credential. 91 // This only makes sense for non-k8s providers. 92 func checkMachineInstances(backend PersistentBackend, provider CloudProvider, callCtx context.ProviderCallContext) (params.ErrorResults, error) { 93 fail := func(original error) (params.ErrorResults, error) { 94 return params.ErrorResults{}, original 95 } 96 97 // Get machines from state 98 machines, err := backend.AllMachines() 99 if err != nil { 100 return fail(errors.Trace(err)) 101 } 102 103 var results []params.ErrorResult 104 105 serverError := func(received error) params.ErrorResult { 106 return params.ErrorResult{Error: common.ServerError(received)} 107 } 108 109 machinesByInstance := make(map[string]string) 110 for _, machine := range machines { 111 if machine.IsContainer() { 112 // Containers don't correspond to instances at the 113 // provider level. 114 continue 115 } 116 if manual, err := machine.IsManual(); err != nil { 117 return fail(errors.Trace(err)) 118 } else if manual { 119 continue 120 } 121 instanceId, err := machine.InstanceId() 122 if errors.IsNotProvisioned(err) { 123 // Skip over this machine; we wouldn't expect the cloud 124 // to know about it. 125 continue 126 } else if err != nil { 127 results = append(results, serverError(errors.Annotatef(err, "getting instance id for machine %s", machine.Id()))) 128 continue 129 } 130 machinesByInstance[string(instanceId)] = machine.Id() 131 } 132 133 // Check can see all machines' instances 134 instances, err := provider.AllInstances(callCtx) 135 if err != nil { 136 return fail(errors.Trace(err)) 137 } 138 139 instanceIds := set.NewStrings() 140 for _, instance := range instances { 141 id := string(instance.Id()) 142 instanceIds.Add(id) 143 if _, found := machinesByInstance[id]; !found { 144 results = append(results, serverError(errors.Errorf("no machine with instance %q", id))) 145 } 146 } 147 148 for instanceId, name := range machinesByInstance { 149 if !instanceIds.Contains(instanceId) { 150 results = append(results, serverError(errors.Errorf("couldn't find instance %q for machine %s", instanceId, name))) 151 } 152 } 153 154 return params.ErrorResults{Results: results}, nil 155 } 156 157 var ( 158 newEnv = environs.New 159 newCAASBroker = caas.New 160 ) 161 162 func buildOpenParams(backend PersistentBackend, credentialTag names.CloudCredentialTag, credential *cloud.Credential) (environs.OpenParams, error) { 163 fail := func(original error) (environs.OpenParams, error) { 164 return environs.OpenParams{}, original 165 } 166 167 model, err := backend.Model() 168 if err != nil { 169 return fail(errors.Trace(err)) 170 } 171 172 modelCloud, err := backend.Cloud(model.Cloud()) 173 if err != nil { 174 return fail(errors.Trace(err)) 175 } 176 177 err = model.ValidateCloudCredential(credentialTag, *credential) 178 if err != nil { 179 return fail(errors.Trace(err)) 180 } 181 182 tempCloudSpec, err := environs.MakeCloudSpec(modelCloud, model.CloudRegion(), credential) 183 if err != nil { 184 return fail(errors.Trace(err)) 185 } 186 187 cfg, err := model.Config() 188 if err != nil { 189 return fail(errors.Trace(err)) 190 } 191 return environs.OpenParams{ 192 Cloud: tempCloudSpec, 193 Config: cfg, 194 }, nil 195 }