Skip to content

Commit

Permalink
vcsim: add HostCertificateManager
Browse files Browse the repository at this point in the history
  • Loading branch information
dougm committed Jun 24, 2024
1 parent 7f0c9f0 commit 072011c
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 37 deletions.
14 changes: 4 additions & 10 deletions govc/library/trust/info.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
Copyright (c) 2022-2022 VMware, Inc. All Rights Reserved.
Copyright (c) 2022-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -18,8 +18,6 @@ package trust

import (
"context"
"crypto/x509"
"encoding/pem"
"flag"
"io"

Expand Down Expand Up @@ -68,15 +66,11 @@ type infoResultsWriter struct {
}

func (r infoResultsWriter) Write(w io.Writer) error {
block, _ := pem.Decode([]byte(r.TrustedCertificateInfo.Text))
x, err := x509.ParseCertificate(block.Bytes)
var info object.HostCertificateInfo
_, err := info.FromPEM([]byte(r.TrustedCertificateInfo.Text))
if err != nil {
return err
}

var info object.HostCertificateInfo
info.FromCertificate(x)

return info.Write(w)
}

Expand Down
15 changes: 13 additions & 2 deletions govc/object/save.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2019-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2019-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -191,7 +191,7 @@ func (cmd *save) save(content []types.ObjectContent) error {
return err
}
dir := filepath.Join(cmd.dir, ref)
if err = os.Mkdir(dir, 0755); err != nil {
if err = os.MkdirAll(dir, 0755); err != nil {
return err
}
for _, obj := range objs {
Expand Down Expand Up @@ -327,6 +327,9 @@ func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
&types.SelectionSpec{
Name: "hostVirtualNicManagerTraversalSpec",
},
&types.SelectionSpec{
Name: "hostCertificateManagerTraversalSpec",
},
&types.SelectionSpec{
Name: "entityTraversalSpec",
},
Expand Down Expand Up @@ -360,6 +363,13 @@ func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
Type: "HostSystem",
Path: "configManager.virtualNicManager",
},
&types.TraversalSpec{
SelectionSpec: types.SelectionSpec{
Name: "hostCertificateManagerTraversalSpec",
},
Type: "HostSystem",
Path: "configManager.certificateManager",
},
&types.TraversalSpec{
SelectionSpec: types.SelectionSpec{
Name: "hostDatastoreSystemTraversalSpec",
Expand All @@ -382,6 +392,7 @@ func (cmd *save) Run(ctx context.Context, f *flag.FlagSet) error {
{Type: "HostDatastoreSystem", All: all},
{Type: "HostNetworkSystem", All: all},
{Type: "HostVirtualNicManager", All: all},
{Type: "HostCertificateManager", All: all},
{Type: "ManagedEntity", All: all},
{Type: "Task", All: all},
},
Expand Down
28 changes: 10 additions & 18 deletions govc/test/host.bats
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ load test_helper
}

@test "host.cert.info" {
esx_env
vcsim_env -esx

run govc host.cert.info
assert_success
Expand All @@ -213,10 +213,16 @@ load test_helper
expires=$(govc host.cert.info -json | jq -r .notAfter)
about_expires=$(govc about.cert -json | jq -r .notAfter)
assert_equal "$expires" "$about_expires"

run govc host.cert.info -show
assert_success

run openssl x509 -text <<<"$output"
assert_success
}

@test "host.cert.csr" {
esx_env
vcsim_env -esx

# Requested Extensions:
# X509v3 Subject Alternative Name:
Expand All @@ -234,33 +240,19 @@ load test_helper
}

@test "host.cert.import" {
esx_env
vcsim_env -esx

issuer=$(govc host.cert.info -json | jq -r .issuer)
expires=$(govc host.cert.info -json | jq -r .notAfter)

# only mess with the cert if its already been signed by our test CA
if [[ "$issuer" != CN=govc-ca,* ]] ; then
skip "host cert not signed by govc-ca"
fi

govc host.cert.csr -ip | ./host_cert_sign.sh | govc host.cert.import
expires2=$(govc host.cert.info -json | jq -r .notAfter)

# cert expiration should have changed
[ "$expires" != "$expires2" ]

# verify hostd is using the new cert too
expires=$(govc about.cert -json | jq -r .notAfter)
expires=$(govc host.cert.info -json | jq -r .notAfter)
assert_equal "$expires" "$expires2"

# our cert is not trusted against the system CA list
status=$(govc about.cert | grep Status:)
assert_matches ERROR "$status"

# with our CA trusted, the cert should be too
status=$(govc about.cert -tls-ca-certs ./govc_ca.pem | grep Status:)
assert_matches good "$status"
}

@test "host.date.info" {
Expand Down
16 changes: 15 additions & 1 deletion object/host_certificate_info.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2016-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2016-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -21,6 +21,8 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"io"
"net/url"
Expand Down Expand Up @@ -66,6 +68,18 @@ func (info *HostCertificateInfo) FromCertificate(cert *x509.Certificate) *HostCe
return info
}

func (info *HostCertificateInfo) FromPEM(cert []byte) (*HostCertificateInfo, error) {
block, _ := pem.Decode(cert)
if block == nil {
return nil, errors.New("failed to pem.Decode cert")
}
x, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
return info.FromCertificate(x), nil
}

// FromURL connects to the given URL.Host via tls.Dial with the given tls.Config and populates the HostCertificateInfo
// via tls.ConnectionState. If the certificate was verified with the given tls.Config, the Err field will be nil.
// Otherwise, Err will be set to the x509.UnknownAuthorityError or x509.HostnameError.
Expand Down
13 changes: 10 additions & 3 deletions object/host_certificate_manager.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
Copyright (c) 2016 VMware, Inc. All Rights Reserved.
Copyright (c) 2016-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -23,6 +23,7 @@ import (
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)

Expand Down Expand Up @@ -117,7 +118,13 @@ func (m HostCertificateManager) InstallServerCertificate(ctx context.Context, ce
Req: &types.Refresh{This: m.Reference()},
}

return m.Client().RoundTrip(ctx, &body, &body)
err = m.Client().RoundTrip(ctx, &body, &body)
if err != nil && soap.IsSoapFault(err) {
if _, ok := soap.ToSoapFault(err).VimFault().(types.MethodNotFound); ok {
return nil
}
}
return err
}

// ListCACertificateRevocationLists returns the SSL CRLs of Certificate Authorities that are trusted by the host system.
Expand Down
4 changes: 2 additions & 2 deletions simulator/esx/host_system.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -1736,7 +1736,7 @@ var HostSystem = mo.HostSystem{
OverallStatus: "gray",
RebootRequired: false,
CustomValue: nil,
ManagementServerIp: "",
ManagementServerIp: "127.0.0.1",
MaxEVCModeKey: "",
CurrentEVCModeKey: "",
Gateway: (*types.HostListSummaryGatewaySummary)(nil),
Expand Down
111 changes: 111 additions & 0 deletions simulator/host_certificate_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
Copyright (c) 2024-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package simulator

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"net"

"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/methods"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/soap"
"github.com/vmware/govmomi/vim25/types"
)

type HostCertificateManager struct {
mo.HostCertificateManager

Host *mo.HostSystem
}

func (m *HostCertificateManager) init(r *Registry) {
for _, obj := range r.objects {
if h, ok := obj.(*HostSystem); ok {
if h.ConfigManager.CertificateManager.Value == m.Self.Value {
m.Host = &h.HostSystem
}
}
}
}

func NewHostCertificateManager(h *mo.HostSystem) *HostCertificateManager {
m := &HostCertificateManager{Host: h}

_ = m.InstallServerCertificate(SpoofContext(), &types.InstallServerCertificate{
Cert: string(m.Host.Config.Certificate),
})

return m
}

func (m *HostCertificateManager) InstallServerCertificate(ctx *Context, req *types.InstallServerCertificate) soap.HasFault {
body := new(methods.InstallServerCertificateBody)

var info object.HostCertificateInfo
cert := []byte(req.Cert)
_, err := info.FromPEM(cert)
if err != nil {
body.Fault_ = Fault(err.Error(), new(types.HostConfigFault))
return body
}

m.CertificateInfo = info.HostCertificateManagerCertificateInfo

m.Host.Config.Certificate = cert

body.Res = new(types.InstallServerCertificateResponse)

return body
}

func (m *HostCertificateManager) GenerateCertificateSigningRequest(ctx *Context, req *types.GenerateCertificateSigningRequest) soap.HasFault {
block, _ := pem.Decode(m.Host.Config.Certificate)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}

csr := x509.CertificateRequest{
Subject: cert.Subject,
SignatureAlgorithm: x509.SHA256WithRSA,
}

if req.UseIpAddressAsCommonName {
csr.IPAddresses = []net.IP{net.ParseIP(m.Host.Summary.ManagementServerIp)}
} else {
csr.DNSNames = []string{m.Host.Name}
}

key, _ := rsa.GenerateKey(rand.Reader, 2048)
der, _ := x509.CreateCertificateRequest(rand.Reader, &csr, key)
var buf bytes.Buffer
err = pem.Encode(&buf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: der})
if err != nil {
panic(err)
}

return &methods.GenerateCertificateSigningRequestBody{
Res: &types.GenerateCertificateSigningRequestResponse{
Returnval: buf.String(),
},
}
}
1 change: 1 addition & 0 deletions simulator/host_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func NewHostSystem(host mo.HostSystem) *HostSystem {
{&hs.ConfigManager.AdvancedOption, NewOptionManager(nil, nil, &hs.Config.Option)},
{&hs.ConfigManager.FirewallSystem, NewHostFirewallSystem(&hs.HostSystem)},
{&hs.ConfigManager.StorageSystem, NewHostStorageSystem(&hs.HostSystem)},
{&hs.ConfigManager.CertificateManager, NewHostCertificateManager(&hs.HostSystem)},
}

for _, c := range config {
Expand Down
1 change: 1 addition & 0 deletions simulator/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ var kinds = map[string]reflect.Type{
"HostDatastoreBrowser": reflect.TypeOf((*HostDatastoreBrowser)(nil)).Elem(),
"HostLocalAccountManager": reflect.TypeOf((*HostLocalAccountManager)(nil)).Elem(),
"HostNetworkSystem": reflect.TypeOf((*HostNetworkSystem)(nil)).Elem(),
"HostCertificateManager": reflect.TypeOf((*HostCertificateManager)(nil)).Elem(),
"HostSystem": reflect.TypeOf((*HostSystem)(nil)).Elem(),
"IpPoolManager": reflect.TypeOf((*IpPoolManager)(nil)).Elem(),
"LicenseManager": reflect.TypeOf((*LicenseManager)(nil)).Elem(),
Expand Down
3 changes: 2 additions & 1 deletion vcsim/main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved.
Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -263,6 +263,7 @@ func updateHostTemplate(ip string) error {
if err != nil {
return err
}
esx.HostSystem.Summary.ManagementServerIp = addr
if port != "0" { // server starts after the model is created, skipping auto-selected ports for now
n, err := strconv.Atoi(port)
if err != nil {
Expand Down

0 comments on commit 072011c

Please sign in to comment.