Fix Windows userdata wrapper and scale set metadata

* The Windows userdata wrapper needs to run the real script with parameters
that allow running a downloaded script and in a non-interactive way.
* The metadata endpoint to get the root CA bundle only worked for pools.
This change fixes it for scale sets as well.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-09-24 19:09:28 +00:00 committed by Gabriel
parent 4763906780
commit 545f042f4d
3 changed files with 39 additions and 37 deletions

View file

@ -1,4 +1,5 @@
$ErrorActionPreference="Stop"
Set-ExecutionPolicy RemoteSigned
function Start-ExecuteWithRetry {
[CmdletBinding()]
@ -49,9 +50,9 @@ function Start-ExecuteWithRetry {
}
}
$installScript = (Join-Path $env:TMP, "garm-install.ps1")
$installScript = (Join-Path $env:TMP "garm-install.ps1")
Start-ExecuteWithRetry -ScriptBlock {
wget -UseBasicParsing -Headers @{"Accept"="application/json"; "Authorization"="Bearer {{ .CallbackToken }}"} -Uri {{ .MetadataURL }}/install-script/ -OutFile $installScript
} -MaxRetryCount 5 -RetryInterval 5 -RetryMessage "Retrying download of runner install script..."
$installScript
powershell.exe -Sta -NonInteractive -ExecutionPolicy RemoteSigned -File $installScript

View file

@ -351,7 +351,7 @@ func (r *Runner) GetInstanceGithubRegistrationToken(ctx context.Context) (string
poolMgr, err := r.getPoolManagerFromInstance(ctx, instance)
if err != nil {
return "", fmt.Errorf("error fetching pool manager for instance: %w", err)
return "", fmt.Errorf("error fetching pool manager for instance %s (%s): %w", instance.Name, instance.PoolID, err)
}
token, err := poolMgr.GithubRunnerRegistrationToken()
@ -383,17 +383,17 @@ func (r *Runner) GetRootCertificateBundle(ctx context.Context) (params.Certifica
return params.CertificateBundle{}, runnerErrors.ErrUnauthorized
}
poolMgr, err := r.getPoolManagerFromInstance(ctx, instance)
entity, err := auth.InstanceEntity(ctx)
if err != nil {
return params.CertificateBundle{}, fmt.Errorf("error fetching pool manager for instance: %w", err)
slog.ErrorContext(r.ctx, "failed to get entity", "error", err)
return params.CertificateBundle{}, runnerErrors.ErrUnauthorized
}
bundle, err := poolMgr.RootCABundle()
bundle, err := entity.Credentials.RootCertificateBundle()
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(
ctx, "failed to get root CA bundle",
"instance", instance.Name,
"pool_manager", poolMgr.ID())
"instance", instance.Name)
// The root CA bundle is invalid. Return an empty bundle to the runner and log the event.
return params.CertificateBundle{
RootCertificates: make(map[string][]byte),

View file

@ -18,6 +18,8 @@ import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/mock"
@ -450,26 +452,6 @@ func (s *MetadataTestSuite) TestGetInstanceGithubRegistrationTokenJITConfig() {
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *MetadataTestSuite) TestGetRootCertificateBundle() {
expectedBundle := params.CertificateBundle{
RootCertificates: map[string][]byte{
"test-ca": []byte("test-certificate"),
},
}
// Set up mocks
s.Fixtures.PoolMgrCtrlMock.On("GetOrgPoolManager", mock.AnythingOfType("params.Organization")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("RootCABundle").Return(expectedBundle, nil)
bundle, err := s.Runner.GetRootCertificateBundle(s.instanceCtx)
s.Require().Nil(err)
s.Require().Equal(expectedBundle.RootCertificates, bundle.RootCertificates)
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
}
func (s *MetadataTestSuite) TestGetRootCertificateBundleUnauthorized() {
_, err := s.Runner.GetRootCertificateBundle(s.unauthorizedCtx)
@ -477,20 +459,39 @@ func (s *MetadataTestSuite) TestGetRootCertificateBundleUnauthorized() {
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *MetadataTestSuite) TestGetRootCertificateBundleInvalidBundle() {
// Set up mocks to return error for invalid bundle
s.Fixtures.PoolMgrCtrlMock.On("GetOrgPoolManager", mock.AnythingOfType("params.Organization")).Return(s.Fixtures.PoolMgrMock, nil)
s.Fixtures.PoolMgrMock.On("RootCABundle").Return(params.CertificateBundle{}, fmt.Errorf("invalid bundle"))
s.Fixtures.PoolMgrMock.On("ID").Return("test-pool-manager-id")
func (s *MetadataTestSuite) TestGetRootCertificateBundleAuthorized() {
// Load a valid test certificate from testdata
certPath := filepath.Join("../testdata/certs", "srv-pub.pem")
testCertPEM, err := os.ReadFile(certPath)
s.Require().NoError(err, "Failed to read test certificate")
bundle, err := s.Runner.GetRootCertificateBundle(s.instanceCtx)
// Set up entity with valid CA bundle
entity := s.Fixtures.TestEntity
entity.Credentials.CABundle = testCertPEM
ctx := auth.SetInstanceParams(context.Background(), s.Fixtures.TestInstance)
ctx = auth.SetInstanceEntity(ctx, entity)
bundle, err := s.Runner.GetRootCertificateBundle(ctx)
s.Require().Nil(err)
s.Require().NotNil(bundle.RootCertificates)
s.Require().NotEmpty(bundle.RootCertificates)
// The test certificate file contains 2 certificates
s.Require().Len(bundle.RootCertificates, 2)
}
func (s *MetadataTestSuite) TestGetRootCertificateBundleInvalidBundle() {
// Set up entity with invalid CA bundle (invalid PEM data)
entity := s.Fixtures.TestEntity
entity.Credentials.CABundle = []byte("bogus cert")
ctx := auth.SetInstanceParams(context.Background(), s.Fixtures.TestInstance)
ctx = auth.SetInstanceEntity(ctx, entity)
bundle, err := s.Runner.GetRootCertificateBundle(ctx)
// Should return empty bundle without error when CA bundle is invalid
s.Require().Nil(err)
s.Require().Empty(bundle.RootCertificates)
s.Fixtures.PoolMgrMock.AssertExpectations(s.T())
s.Fixtures.PoolMgrCtrlMock.AssertExpectations(s.T())
}
func TestMetadataTestSuite(t *testing.T) {