Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support running SPIRE as a Windows service #3625

Merged
merged 5 commits into from Jan 4, 2023

Conversation

amartinezfayo
Copy link
Member

Add support in SPIRE to run both SPIRE Server and SPIRE Agent as a Windows service.

Fixes #3490.

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>

status := <-testCase.sc.statusCh
assert.Equal(t, svc.Running, status.State)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @amartinezfayo, what do you think about making an interrogation here so we can cover this path asserting it returns the current state?

Suggested change
// Interrogate the service, which should return the current status.
testCase.sc.changeRequestCh <- svc.ChangeRequest{Cmd: svc.Interrogate}
status = <-testCase.sc.statusCh

)

func main() {
os.Exit(new(cli.CLI).Run(os.Args[1:]))
os.Exit(entrypoint.NewEntryPoint(new(cli.CLI).Run).Main())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that with this approach, any command will try to verify if our binary is running as a service,
but that is only true for "run" commands

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this and we agreed that in order to avoid blocking SPIRE to run only because the svc.IsWindowsService() fails, we will fallback to run SPIRE as a regular process if IsWindowsService() returns an error.

Comment on lines +246 to +249
ctx := cmd.ctx
if ctx == nil {
ctx = context.Background()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may this initialization be moved to newRunCommand or NewRunCommand?
I know this is a double check... but sounds like if we require a context it must be set on struct initialization

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there is a real benefit of doing that. Currently, the context is being initialized here also, there is no change with that (https://github.com/spiffe/spire/blob/main/cmd/spire-server/cli/run/run.go#L244).
The cost of moving this to newRunCommand would be that if the struct is being initialized without calling the newRunCommand function (like we currently do in unit tests), we would need to set ctx there. The intention of having it here is that a context is always provided if not specified.
What do you think?

@@ -166,6 +166,13 @@ the following flags are available:
| `-trustBundleUrl` | URL to download the SPIRE server CA bundle | |
| `-trustDomain` | The trust domain that this agent belongs to (should be no more than 255 characters) | |

#### Running SPIRE Agent as a Windows service

On Windows platform, SPIRE Agent can optionally be run as a Windows service. When running as a Windows service, the only command supported is the `run` command.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is this validation done? may we prevent it programmatically? what happens if a user try to create a service that run ./bin/spire-server bundle show (it is unrealistic example)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a validation to fail with ERROR_BAD_ARGUMENTS if an argument indicating a command different to "run" is used when running as a service.

On Windows platform, SPIRE Agent can optionally be run as a Windows service. When running as a Windows service, the only command supported is the `run` command.

_Note: SPIRE does not automatically create the service in the system, it must be created by the user.
When starting the service, all the arguments to execute SPIRE Agent with the `run` command must be passed as service arguments._
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is something useful to provide an example here of a configuration that allows this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'll add an example of how the service can be created and how the service can be run.

handler: &service{
RunFn: func(ctx context.Context, stop context.CancelFunc, args []string) int {
retCode := runCmdFn(ctx, args[1:])
stop()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may we move stop to a defer?

Comment on lines 96 to 97
changeRequestCh: make(chan svc.ChangeRequest, 1),
statusCh: make(chan svc.Status, 1),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: those channels can be created on test body, when isWindowsService == true

ep := newEntryPoint(func(ctx context.Context, args []string) int {
return testCase.retCode
}, testCase.sc)
retCodeCh <- ep.Main()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when main is closed? may we add a select with this + ctx.Done()?

// This is running as a service.
// Check if we expect a failure running the service.
if testCase.retCode != 0 {
assert.Equal(t, testCase.retCode, <-retCodeCh)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about adding a select here too? so we can be sure we return something before our ctx is finished (ctx with timeout)

// Final status should be Stopped.
assert.Equal(t, svc.Stopped, (<-testCase.sc.statusCh).State)

assert.Equal(t, false, testCase.sc.svcSpecificEC)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert.IsFalse

}

status <- svc.Status{State: svc.StopPending}
stop()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defer after creation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be here to prevent races with the retCode.

@amartinezfayo amartinezfayo modified the milestones: 1.5.2, 1.5.3 Dec 1, 2022
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Copy link
Collaborator

@MarcosDY MarcosDY left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great a couple comments on docs to pass lints

@@ -173,6 +173,18 @@ On Windows platform, SPIRE Agent can optionally be run as a Windows service. Whe
_Note: SPIRE does not automatically create the service in the system, it must be created by the user.
When starting the service, all the arguments to execute SPIRE Agent with the `run` command must be passed as service arguments._

##### Example to create the SPIRE Agent Windows service

```
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you will need to add type here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type is already added, I think that this was commented from a previous commit.

##### Example to create the SPIRE Agent Windows service

```
sc.exe create spire-agent binpath=c:\spire\bin\spire-agent.exe
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it is a shell may we add $ at the start?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> probably fits better for a Windows terminal / command prompt?

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Copy link
Collaborator

@MarcosDY MarcosDY left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!!

@amartinezfayo amartinezfayo merged commit a7df8a5 into spiffe:main Jan 4, 2023
@amartinezfayo amartinezfayo deleted the win-service-support branch March 1, 2023 17:59
stevend-uber pushed a commit to stevend-uber/spire that referenced this pull request Oct 16, 2023
* Support running SPIRE as a Windows service

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Service State Transitions on Windows
3 participants