Skip to content

Commit ebf43c9

Browse files
committed
Add support for Azure workload identity tokens
Add new featuregate access mechanism from library-go for detecting whether AWZI is enabled or disabled. This access mechanism can also be used for future featuregates. Update azidentity to latest version and add the option to use Azure Workload Identity (AZWI) as an authentication method for Azure. The controller can either authenticate using config values from the standard secret, or from standard Azure environment variables. A missing/unset/empty client_secret value will cause the controller to attempt to authenticate with AZWI. Pull the resourceGroup from the cluster infrastructure if it isn't set in the secret Update depricated method in the termination handler, which was causing unit tests to time out and fail
1 parent 3afc1c7 commit ebf43c9

File tree

6 files changed

+229
-53
lines changed

6 files changed

+229
-53
lines changed

cmd/manager/main.go

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,27 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"context"
2021
"flag"
22+
"fmt"
2123
"os"
2224
"time"
2325

2426
configv1 "github.com/openshift/api/config/v1"
2527
machinev1 "github.com/openshift/api/machine/v1beta1"
28+
configclient "github.com/openshift/client-go/config/clientset/versioned"
29+
configinformers "github.com/openshift/client-go/config/informers/externalversions"
30+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
31+
"github.com/openshift/library-go/pkg/operator/events"
2632
"github.com/openshift/machine-api-operator/pkg/controller/machine"
2733
"github.com/openshift/machine-api-operator/pkg/metrics"
2834
actuator "github.com/openshift/machine-api-provider-azure/pkg/cloud/azure/actuators/machine"
2935
machinesetcontroller "github.com/openshift/machine-api-provider-azure/pkg/cloud/azure/actuators/machineset"
3036
"github.com/openshift/machine-api-provider-azure/pkg/cloud/azure/services/resourceskus"
3137
"github.com/openshift/machine-api-provider-azure/pkg/record"
38+
corev1 "k8s.io/api/core/v1"
39+
"k8s.io/client-go/kubernetes"
40+
"k8s.io/client-go/rest"
3241
"k8s.io/klog/v2"
3342
"k8s.io/klog/v2/klogr"
3443
ctrl "sigs.k8s.io/controller-runtime"
@@ -116,11 +125,36 @@ func main() {
116125
// Initialize event recorder.
117126
record.InitFromRecorder(mgr.GetEventRecorderFor("azure-controller"))
118127

128+
stopSignalContext := ctrl.SetupSignalHandler()
129+
130+
featureGateAccessor, err := createFeatureGateAccessor(
131+
context.Background(),
132+
cfg,
133+
"machine-api-provider-azure",
134+
"openshift-machine-api",
135+
"machine-api-controllers",
136+
getReleaseVersion(),
137+
"0.0.1-snapshot",
138+
syncPeriod,
139+
stopSignalContext.Done(),
140+
)
141+
if err != nil {
142+
klog.Fatalf("Failed to create feature gate accessor: %w", err)
143+
}
144+
145+
featureGates, err := awaitEnabledFeatureGates(featureGateAccessor, 1*time.Minute)
146+
if err != nil {
147+
klog.Fatalf("Failed to get feature gates: %w", err)
148+
}
149+
150+
azureWorkloadIdentityEnabled := featureGates.Enabled(configv1.FeatureGateAzureWorkloadIdentity)
151+
119152
// Initialize machine actuator.
120153
machineActuator := actuator.NewActuator(actuator.ActuatorParams{
121-
CoreClient: mgr.GetClient(),
122-
ReconcilerBuilder: actuator.NewReconciler,
123-
EventRecorder: mgr.GetEventRecorderFor("azure-controller"),
154+
CoreClient: mgr.GetClient(),
155+
ReconcilerBuilder: actuator.NewReconciler,
156+
EventRecorder: mgr.GetEventRecorderFor("azure-controller"),
157+
AzureWorkloadIdentityEnabled: azureWorkloadIdentityEnabled,
124158
})
125159

126160
if err := machinev1.AddToScheme(mgr.GetScheme()); err != nil {
@@ -141,6 +175,8 @@ func main() {
141175
Client: mgr.GetClient(),
142176
Log: ctrl.Log.WithName("controllers").WithName("MachineSet"),
143177
ResourceSkusServiceBuilder: resourceskus.NewService,
178+
179+
AzureWorkloadIdentityEnabled: azureWorkloadIdentityEnabled,
144180
}).SetupWithManager(mgr, controller.Options{}); err != nil {
145181
setupLog.Error(err, "unable to create controller", "controller", "MachineSet")
146182
os.Exit(1)
@@ -154,7 +190,66 @@ func main() {
154190
klog.Fatal(err)
155191
}
156192

157-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
193+
if err := mgr.Start(stopSignalContext); err != nil {
158194
klog.Fatalf("Failed to run manager: %v", err)
159195
}
160196
}
197+
198+
func createFeatureGateAccessor(ctx context.Context, cfg *rest.Config, operatorName, deploymentNamespace, deploymentName, desiredVersion, missingVersion string, syncPeriod time.Duration, stop <-chan struct{}) (featuregates.FeatureGateAccess, error) {
199+
ctx, cancelFn := context.WithCancel(ctx)
200+
go func() {
201+
defer cancelFn()
202+
<-stop
203+
}()
204+
205+
kubeClient, err := kubernetes.NewForConfig(cfg)
206+
if err != nil {
207+
return nil, fmt.Errorf("failed to create kube client: %w", err)
208+
}
209+
210+
eventRecorder := events.NewKubeRecorder(kubeClient.CoreV1().Events(deploymentNamespace), operatorName, &corev1.ObjectReference{
211+
APIVersion: "apps/v1",
212+
Kind: "Deployment",
213+
Namespace: deploymentNamespace,
214+
Name: deploymentName,
215+
})
216+
217+
configClient, err := configclient.NewForConfig(cfg)
218+
if err != nil {
219+
return nil, fmt.Errorf("failed to create config client: %w", err)
220+
}
221+
configInformers := configinformers.NewSharedInformerFactory(configClient, syncPeriod)
222+
223+
featureGateAccessor := featuregates.NewFeatureGateAccess(
224+
desiredVersion, missingVersion,
225+
configInformers.Config().V1().ClusterVersions(), configInformers.Config().V1().FeatureGates(),
226+
eventRecorder,
227+
)
228+
go featureGateAccessor.Run(ctx)
229+
go configInformers.Start(stop)
230+
231+
return featureGateAccessor, nil
232+
}
233+
234+
func awaitEnabledFeatureGates(accessor featuregates.FeatureGateAccess, timeout time.Duration) (featuregates.FeatureGate, error) {
235+
select {
236+
case <-accessor.InitialFeatureGatesObserved():
237+
featureGates, err := accessor.CurrentFeatureGates()
238+
if err != nil {
239+
return nil, err
240+
} else {
241+
klog.Infof("FeatureGates initialized: knownFeatureGates=%v", featureGates.KnownFeatures())
242+
return featureGates, nil
243+
}
244+
case <-time.After(timeout):
245+
return nil, fmt.Errorf("timed out waiting for FeatureGate detection")
246+
}
247+
}
248+
249+
func getReleaseVersion() string {
250+
releaseVersion := os.Getenv("RELEASE_VERSION")
251+
if len(releaseVersion) == 0 {
252+
return "0.0.1-snapshot"
253+
}
254+
return releaseVersion
255+
}

pkg/cloud/azure/actuators/machine/actuator.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,25 @@ type Actuator struct {
5252
eventRecorder record.EventRecorder
5353

5454
reconcilerBuilder func(scope *actuators.MachineScope) *Reconciler
55+
56+
azureWorkloadIdentityEnabled bool
5557
}
5658

5759
// ActuatorParams holds parameter information for Actuator.
5860
type ActuatorParams struct {
59-
CoreClient controllerclient.Client
60-
EventRecorder record.EventRecorder
61-
ReconcilerBuilder func(scope *actuators.MachineScope) *Reconciler
61+
CoreClient controllerclient.Client
62+
EventRecorder record.EventRecorder
63+
ReconcilerBuilder func(scope *actuators.MachineScope) *Reconciler
64+
AzureWorkloadIdentityEnabled bool
6265
}
6366

6467
// NewActuator returns an actuator.
6568
func NewActuator(params ActuatorParams) *Actuator {
6669
return &Actuator{
67-
coreClient: params.CoreClient,
68-
eventRecorder: params.EventRecorder,
69-
reconcilerBuilder: params.ReconcilerBuilder,
70+
coreClient: params.CoreClient,
71+
eventRecorder: params.EventRecorder,
72+
reconcilerBuilder: params.ReconcilerBuilder,
73+
azureWorkloadIdentityEnabled: params.AzureWorkloadIdentityEnabled,
7074
}
7175
}
7276

@@ -86,8 +90,9 @@ func (a *Actuator) Create(ctx context.Context, machine *machinev1.Machine) error
8690
klog.Infof("Creating machine %v", machine.Name)
8791

8892
scope, err := actuators.NewMachineScope(actuators.MachineScopeParams{
89-
Machine: machine,
90-
CoreClient: a.coreClient,
93+
Machine: machine,
94+
CoreClient: a.coreClient,
95+
AzureWorkloadIdentityEnabled: a.azureWorkloadIdentityEnabled,
9196
})
9297
if err != nil {
9398
return a.handleMachineError(machine, machineapierrors.InvalidMachineConfiguration("failed to create machine %q scope: %v", machine.Name, err), createEventAction)
@@ -139,8 +144,9 @@ func (a *Actuator) Delete(ctx context.Context, machine *machinev1.Machine) error
139144
klog.Infof("Deleting machine %v", machine.Name)
140145

141146
scope, err := actuators.NewMachineScope(actuators.MachineScopeParams{
142-
Machine: machine,
143-
CoreClient: a.coreClient,
147+
Machine: machine,
148+
CoreClient: a.coreClient,
149+
AzureWorkloadIdentityEnabled: a.azureWorkloadIdentityEnabled,
144150
})
145151
if err != nil {
146152
return a.handleMachineError(machine, machineapierrors.DeleteMachine("failed to create machine %q scope: %v", machine.Name, err), deleteEventAction)
@@ -174,8 +180,9 @@ func (a *Actuator) Update(ctx context.Context, machine *machinev1.Machine) error
174180
klog.Infof("Updating machine %v", machine.Name)
175181

176182
scope, err := actuators.NewMachineScope(actuators.MachineScopeParams{
177-
Machine: machine,
178-
CoreClient: a.coreClient,
183+
Machine: machine,
184+
CoreClient: a.coreClient,
185+
AzureWorkloadIdentityEnabled: a.azureWorkloadIdentityEnabled,
179186
})
180187
if err != nil {
181188
return a.handleMachineError(machine, machineapierrors.UpdateMachine("failed to create machine %q scope: %v", machine.Name, err), updateEventAction)
@@ -214,8 +221,9 @@ func (a *Actuator) Exists(ctx context.Context, machine *machinev1.Machine) (bool
214221
klog.Infof("%s: actuator checking if machine exists", machine.GetName())
215222

216223
scope, err := actuators.NewMachineScope(actuators.MachineScopeParams{
217-
Machine: machine,
218-
CoreClient: a.coreClient,
224+
Machine: machine,
225+
CoreClient: a.coreClient,
226+
AzureWorkloadIdentityEnabled: a.azureWorkloadIdentityEnabled,
219227
})
220228
if err != nil {
221229
return false, fmt.Errorf("failed to create scope: %+v", err)

pkg/cloud/azure/actuators/machine/actuator_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ func TestMachineEvents(t *testing.T) {
589589
operation: func(actuator *Actuator, machine *machinev1.Machine) {
590590
actuator.Create(context.TODO(), machine)
591591
},
592-
event: "Warning FailedCreate InvalidConfiguration: failed to create machine \"azure-actuator-testing-machine\" scope: failed to update cluster: Azure client id default/azure-credentials-secret did not contain key azure_client_id",
592+
event: "Warning FailedCreate InvalidConfiguration: failed to create machine \"azure-actuator-testing-machine\" scope: failed to update cluster: azure client id not found in secret default/azure-credentials-secret (azure_client_id) or environment variable AZURE_CLIENT_ID",
593593
},
594594
{
595595
name: "Create machine event failed (reconciler)",
@@ -616,7 +616,7 @@ func TestMachineEvents(t *testing.T) {
616616
operation: func(actuator *Actuator, machine *machinev1.Machine) {
617617
actuator.Update(context.TODO(), machine)
618618
},
619-
event: "Warning FailedUpdate UpdateError: failed to create machine \"azure-actuator-testing-machine\" scope: failed to update cluster: Azure client id default/azure-credentials-secret did not contain key azure_client_id",
619+
event: "Warning FailedUpdate UpdateError: failed to create machine \"azure-actuator-testing-machine\" scope: failed to update cluster: azure client id not found in secret default/azure-credentials-secret (azure_client_id) or environment variable AZURE_CLIENT_ID",
620620
},
621621
{
622622
name: "Update machine event succeed",
@@ -634,7 +634,7 @@ func TestMachineEvents(t *testing.T) {
634634
operation: func(actuator *Actuator, machine *machinev1.Machine) {
635635
actuator.Delete(context.TODO(), machine)
636636
},
637-
event: "Warning FailedDelete DeleteError: failed to create machine \"azure-actuator-testing-machine\" scope: failed to update cluster: Azure client id default/azure-credentials-secret did not contain key azure_client_id",
637+
event: "Warning FailedDelete DeleteError: failed to create machine \"azure-actuator-testing-machine\" scope: failed to update cluster: azure client id not found in secret default/azure-credentials-secret (azure_client_id) or environment variable AZURE_CLIENT_ID",
638638
},
639639
{
640640
name: "Delete machine event failed (reconciler)",

0 commit comments

Comments
 (0)