Skip to content

Commit

Permalink
Implement certificate policy manager for machines (#745)
Browse files Browse the repository at this point in the history
  • Loading branch information
GabrielNagy committed Jul 31, 2023
2 parents f209ea7 + 1a7be22 commit 1efd21e
Show file tree
Hide file tree
Showing 73 changed files with 2,899 additions and 51 deletions.
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

0 comments on commit 1efd21e

Please sign in to comment.