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

Certificate autoenrollment POC #743

Closed
wants to merge 2 commits into from
Closed
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.

30 changes: 29 additions & 1 deletion cmd/adsysd/client/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,20 @@ func (a *App) installPolicy() {
policyCmd.AddCommand(debugCmd)
gpoListCmd := &cobra.Command{
Use: "gpolist-script",
Short: i18n.G("Write GPO list python embeeded script in current directory"),
Short: i18n.G("Write GPO list python embedded script in current directory"),
Args: cobra.NoArgs,
ValidArgsFunction: cmdhandler.NoValidArgs,
RunE: func(cmd *cobra.Command, args []string) error { return a.dumpGPOListScript() },
}
debugCmd.AddCommand(gpoListCmd)
certEnrollCmd := &cobra.Command{
Use: "cert-autoenroll-script",
Short: i18n.G("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
4 changes: 4 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ override_dh_auto_install:
cp -a systemd/*.timer debian/tmp/lib/systemd/system/
cp -a systemd/user/*.service debian/tmp/usr/lib/systemd/user/

# vendored python modules
mkdir -p debian/tmp/usr/share/adsys
cp -a internal/policies/certificate/python debian/tmp/usr/share/adsys/

# Separate windows binaries
ifeq ($(WINDOWS_BUILD),1)
mkdir -p debian/tmp/usr/share/adsys/windows
Expand Down
2 changes: 1 addition & 1 deletion internal/ad/ad.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func (ad *AD) parseGPOs(ctx context.Context, gpos []gpo, objectClass ObjectClass
defer ad.downloadables[name].mu.RUnlock()
_ = ad.downloadables[name].testConcurrent

log.Debugf(ctx, "Parsing GPO %q", name)
log.Debugf(ctx, "Parsing GPO %q with ID %q", name, gpoWithRules.ID)

// We need to consider the uppercase version of the name as well,
// which could occur in some of the default GPOs such as Default
Expand Down
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
3 changes: 3 additions & 0 deletions internal/consts/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ const (
// DefaultCacheDir is the default path for adsys system cache directory.
DefaultCacheDir = "/var/cache/adsys"

// DefaultStateDir is the default path for adsys system state directory.
DefaultStateDir = "/var/lib/adsys"

// DefaultRunDir is the default path for adsys run directory.
DefaultRunDir = "/run/adsys"

Expand Down
88 changes: 88 additions & 0 deletions internal/policies/certificate/cert-autoenroll
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/python3

import argparse
import os
import sys
import tempfile

from collections import namedtuple

from samba import param
from samba.credentials import MUST_USE_KERBEROS, Credentials

from vendor_samba.gp.gpclass import get_dc_hostname, get_deleted_gpos_list, GPOStorage
from vendor_samba.gp import gp_cert_auto_enroll_ext as cae

GPO = namedtuple('GPO', ['file_sys_path', 'name'])

PRIVATE_DIR = '/var/lib/adsys/private/certs'
TRUST_DIR = '/var/lib/adsys/certs'

class adsys_cert_auto_enroll(cae.gp_cert_auto_enroll_ext):
# the default implementation overrides the path to the pol file, we don't want that
# also account for the case insensitivity of the filesystem
def parse(self, path):
if os.path.isfile(path):
return self.read(path)

policy_path = os.path.dirname(os.path.dirname(path))
for f in os.listdir(policy_path):
if f.lower() == 'machine':
return self.read(os.path.join(policy_path, f, 'Registry.pol'))

raise Exception('Could not find policy file for %s' % path)

def smb_config(realm, enable_debug):
config = "[global]\nrealm = %s\n" % realm
if enable_debug:
config += "log level = 10\n"
return config

def main():
parser = argparse.ArgumentParser(description='Certificate autoenrollment via Samba')
parser.add_argument('realm', type=str,
help='The realm of the domain, e.g. example.com')
parser.add_argument('gpo_paths', type=str,
help='Comma-separated list of paths to the GPOs to apply. \
e.g. /var/cache/adsys/sysvol/Policies/{31B2F340-016D-11D2-945F-00C04FB984F9}')
parser.add_argument('--state-dir', type=str,
default='/var/lib/adsys',
help='Directory to store GPO state in.')
parser.add_argument('--debug', action='store_true',
help='Enable samba debug output.')

args = parser.parse_args()

gpo_paths = args.gpo_paths.split(',')
state_dir = args.state_dir

# Create needed directories if they don't exist
for directory in [state_dir, TRUST_DIR, PRIVATE_DIR]:
if not os.path.exists(directory):
os.makedirs(directory)

gpos = [GPO(gpo, os.path.basename(gpo)) for gpo in gpo_paths]

c = Credentials()
c.set_kerberos_state(MUST_USE_KERBEROS)

with tempfile.NamedTemporaryFile(prefix='smb_conf') as smb_conf:
smb_conf.write(smb_config(args.realm, args.debug).encode('utf-8'))
smb_conf.flush()

lp = param.LoadParm(smb_conf.name)
c.guess(lp)

store = GPOStorage(os.path.join(state_dir, 'cert_gpo_state.tdb'))

username = c.get_username()
gp_db = store.get_gplog(username)
deleted_gpos = get_deleted_gpos_list(gp_db, gpos)
ext = adsys_cert_auto_enroll(lp, c, username, store)
ext.process_group_policy(deleted_gpos, gpos,
trust_dir='/var/lib/adsys/certs',
private_dir='/var/lib/adsys/private/certs',
global_trust_dir='/usr/local/share/ca-certificates')

if __name__ == "__main__":
sys.exit(main())
Loading