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

Implement certificate policy manager for machines #745

Merged
merged 13 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions adsys.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions adsys.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ service service {
rpc ListDoc(ListDocRequest) returns (stream StringResponse);
rpc ListUsers(ListUsersRequest) returns (stream StringResponse);
rpc GPOListScript(Empty) returns (stream StringResponse);
rpc CertAutoEnrollScript(Empty) returns (stream StringResponse);
}

message Empty {}
Expand Down
64 changes: 64 additions & 0 deletions adsys_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions cmd/adsysd/client/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ func (a *App) installPolicy() {
RunE: func(cmd *cobra.Command, args []string) error { return a.dumpGPOListScript() },
}
debugCmd.AddCommand(gpoListCmd)
certEnrollCmd := &cobra.Command{
Use: "cert-autoenroll-script",
Short: gotext.Get("Write certificate autoenrollment python embedded script in current directory"),
Args: cobra.NoArgs,
ValidArgsFunction: cmdhandler.NoValidArgs,
RunE: func(cmd *cobra.Command, args []string) error { return a.dumpCertEnrollScript() },
}
debugCmd.AddCommand(certEnrollCmd)

var updateMachine, updateAll *bool
updateCmd := &cobra.Command{
Expand Down Expand Up @@ -271,6 +279,26 @@ func (a *App) dumpGPOListScript() error {
return os.WriteFile("adsys-gpolist", []byte(script), 0600)
}

func (a *App) dumpCertEnrollScript() error {
client, err := adsysservice.NewClient(a.config.Socket, a.getTimeout())
if err != nil {
return err
}
defer client.Close()

stream, err := client.CertAutoEnrollScript(a.ctx, &adsys.Empty{})
if err != nil {
return err
}

script, err := singleMsg(stream)
if err != nil {
return err
}

return os.WriteFile("cert-autoenroll", []byte(script), 0600)
}

func colorizePolicies(policies string) (string, error) {
first := true
var out stringsBuilderWithError
Expand Down
33 changes: 20 additions & 13 deletions cmd/adsysd/integration_tests/adsysctl_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1088,48 +1088,55 @@ func TestPolicyUpdate(t *testing.T) {
}
}

func TestPolicyDebugGPOListScript(t *testing.T) {
gpolistSrc, err := os.ReadFile(filepath.Join(rootProjectDir, "internal/ad/adsys-gpolist"))
require.NoError(t, err, "Setup: failed to load source of adsys-gpolist")

func TestPolicyDebugScriptDump(t *testing.T) {
tests := map[string]struct {
script string
cmdName string
path string

systemAnswer string
daemonNotStarted bool

wantErr bool
}{
"Get adsys-gpolist script": {systemAnswer: "polkit_yes"},
"Version is always authorized": {systemAnswer: "polkit_no"},
"Get adsys-gpolist script": {script: "adsys-gpolist", cmdName: "gpolist-script", path: "internal/ad", systemAnswer: "polkit_yes"},
"Get cert-autoenroll script": {script: "cert-autoenroll", cmdName: "cert-autoenroll-script", path: "internal/policies/certificate", systemAnswer: "polkit_yes"},
"adsys-gpolist is always authorized": {script: "adsys-gpolist", cmdName: "gpolist-script", path: "internal/ad", systemAnswer: "polkit_no"},
"cert-autoenroll is always authorized": {script: "cert-autoenroll", cmdName: "cert-autoenroll-script", path: "internal/policies/certificate", systemAnswer: "polkit_no"},

"Error on daemon not responding": {daemonNotStarted: true, wantErr: true},
"Error on daemon not responding for adsys-gpolist": {script: "adsys-gpolist", cmdName: "gpolist-script", path: "internal/ad", daemonNotStarted: true, wantErr: true},
"Error on daemon not responding for cert-autoenroll": {script: "cert-autoenroll", cmdName: "cert-autoenroll-script", path: "internal/policies/certificate", daemonNotStarted: true, wantErr: true},
}
for name, tc := range tests {
tc := tc
t.Run(name, func(t *testing.T) {
dbusAnswer(t, tc.systemAnswer)

scriptSrc, err := os.ReadFile(filepath.Join(rootProjectDir, tc.path, tc.script))
require.NoError(t, err, "Setup: failed to load source of %s script", tc.script)

conf := createConf(t)
if !tc.daemonNotStarted {
defer runDaemon(t, conf)()
}

testutils.Chdir(t, os.TempDir())

_, err := runClient(t, conf, "policy", "debug", "gpolist-script")
_, err = runClient(t, conf, "policy", "debug", tc.cmdName)
if tc.wantErr {
require.Error(t, err, "client should exit with an error")
return
}

f, err := os.Stat("adsys-gpolist")
require.NoError(t, err, "gpo list script should exists")
f, err := os.Stat(tc.script)
require.NoError(t, err, "%s script should exists", tc.script)

require.NotEqual(t, 0, f.Mode()&0111, "Script should be executable")

got, err := os.ReadFile("adsys-gpolist")
require.NoError(t, err, "gpo list script is not readable")
got, err := os.ReadFile(tc.script)
require.NoError(t, err, "%s script is not readable", tc.script)

require.Equal(t, string(gpolistSrc), string(got), "Script content should match source")
require.Equal(t, string(scriptSrc), string(got), "Script content should match source")
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Next Refresh: Tue May 25 14:55

Ubuntu Pro subscription is not active on this machine. Rules belonging to the following policy types will not be applied:
- apparmor
- certificate
- mount
- privilege
- proxy
Expand Down
17 changes: 17 additions & 0 deletions internal/ad/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ const (
UserObject ObjectClass = "user"
// ComputerObject is a computer representation in AD.
ComputerObject ObjectClass = "computer"

// certAutoEnrollKey is the GPO entry that configures certificate autoenrollment.
certAutoEnrollKey string = "Software/Policies/Microsoft/Cryptography/AutoEnrollment/AEPolicy"

// policyServerPrefix is the GPO prefix containing keys that configure
// policy servers for certificate enrollment.
policyServersPrefix string = "Software/Policies/Microsoft/Cryptography/PolicyServers/"
)

type gpo downloadable
Expand Down Expand Up @@ -522,6 +529,16 @@ func (ad *AD) parseGPOs(ctx context.Context, gpos []gpo, objectClass ObjectClass
var currentKey string
var overrideEnabled bool
for _, pol := range pols {
// Rewrite the certificate autoenrollment key so we can easily
// use it in the policy manager
if pol.Key == certAutoEnrollKey {
pol.Key = fmt.Sprintf("%scertificate/autoenroll/all", keyFilterPrefix)
}

if strings.HasPrefix(pol.Key, policyServersPrefix) {
pol.Key = fmt.Sprintf("%scertificate/%s/all", keyFilterPrefix, pol.Key)
}

// Only consider supported policies for this distro
if !strings.HasPrefix(pol.Key, keyFilterPrefix) {
continue
Expand Down
18 changes: 18 additions & 0 deletions internal/ad/ad_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,24 @@ func TestGetPolicies(t *testing.T) {
}}},
}},
},
"Include non Ubuntu keys used to configure certificate autoenrollment": {
objectName: hostname,
objectClass: ad.ComputerObject,
gpoListArgs: []string{"gpoonly.com", hostname + ":filtered-with-certificate-autoenrollment"},
want: policies.Policies{GPOs: []policies.GPO{
{ID: "filtered-with-certificate-autoenrollment", Name: "filtered-with-certificate-autoenrollment-name", Rules: map[string][]entry.Entry{
"certificate": {
{Key: "autoenroll", Value: "1"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/Flags", Value: "0"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/37c9dc30f207f27f61a2f7c3aed598a6e2920b54/URL", Value: "LDAP:"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/37c9dc30f207f27f61a2f7c3aed598a6e2920b54/PolicyID", Value: "{A5E9BF57-71C6-443A-B7FC-79EFA6F73EBD}"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/37c9dc30f207f27f61a2f7c3aed598a6e2920b54/FriendlyName", Value: "Active Directory Enrollment Policy"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/37c9dc30f207f27f61a2f7c3aed598a6e2920b54/Flags", Value: "20"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/37c9dc30f207f27f61a2f7c3aed598a6e2920b54/AuthFlags", Value: "2"},
{Key: "Software/Policies/Microsoft/Cryptography/PolicyServers/37c9dc30f207f27f61a2f7c3aed598a6e2920b54/Cost", Value: "2147483645"},
}}},
}},
},
"Ignore errors on non Ubuntu keys": {
gpoListArgs: []string{"gpoonly.com", "bob:unsupported-with-errors"},
want: policies.Policies{GPOs: []policies.GPO{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[General]
Version=1000
displayName=New Group Policy Object
Binary file not shown.
2 changes: 1 addition & 1 deletion internal/adsysservice/adsysservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ func New(ctx context.Context, opts ...option) (s *Service, err error) {
if args.systemUnitDir != "" {
policyOptions = append(policyOptions, policies.WithSystemUnitDir(args.systemUnitDir))
}
m, err := policies.NewManager(bus, hostname, policyOptions...)
m, err := policies.NewManager(bus, hostname, adBackend, policyOptions...)
if err != nil {
return nil, err
}
Expand Down
18 changes: 18 additions & 0 deletions internal/adsysservice/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
log "github.com/ubuntu/adsys/internal/grpc/logstreamer"
"github.com/ubuntu/adsys/internal/i18n"
"github.com/ubuntu/adsys/internal/policies"
"github.com/ubuntu/adsys/internal/policies/certificate"
"github.com/ubuntu/decorate"
"golang.org/x/sync/errgroup"
)
Expand Down Expand Up @@ -156,4 +157,21 @@ func (s *Service) GPOListScript(_ *adsys.Empty, stream adsys.Service_GPOListScri
return nil
}

// CertAutoEnrollScript returns the embedded certificate autoenrollment python script.
func (s *Service) CertAutoEnrollScript(_ *adsys.Empty, stream adsys.Service_CertAutoEnrollScriptServer) (err error) {
defer decorate.OnError(&err, i18n.G("error while getting certificate autoenrollment script"))

if err := s.authorizer.IsAllowedFromContext(stream.Context(), authorizer.ActionAlwaysAllowed); err != nil {
return err
}

if err := stream.Send(&adsys.StringResponse{
Msg: certificate.CertEnrollCode,
}); err != nil {
log.Warningf(stream.Context(), "couldn't send certificate autoenrollment script to client: %v", err)
}

return nil
}

// FIXME: check cache file permission
Loading