Skip to content

Commit

Permalink
Add support for storing RDP licenses from Windows Desktops (#51685)
Browse files Browse the repository at this point in the history
Signed-off-by: Zac Bergquist <[email protected]>
Co-authored-by: Przemko Robakowski <[email protected]>
Co-authored-by: Alan Parra <[email protected]>
  • Loading branch information
3 people authored Feb 6, 2025
1 parent d0c2b82 commit b57591d
Show file tree
Hide file tree
Showing 13 changed files with 419 additions and 48 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/dependency-review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,21 @@ jobs:
allow-ghsas: 'GHSA-6xf3-5hp7-xqqg'
# IronRDP uses MIT/Apache-2.0 but slashes are not recognized by dependency review action
allow-dependencies-licenses: >-
pkg:cargo/ironrdp-cliprdr,
pkg:cargo/ironrdp-core,
pkg:cargo/ironrdp-async,
pkg:cargo/ironrdp-connector,
pkg:cargo/ironrdp-displaycontrol,
pkg:cargo/ironrdp-dvc,
pkg:cargo/ironrdp-error,
pkg:cargo/ironrdp-graphics,
pkg:cargo/ironrdp-pdu,
pkg:cargo/ironrdp-rdpdr,
pkg:cargo/ironrdp-rdpsnd,
pkg:cargo/ironrdp-session,
pkg:cargo/ironrdp-svc,
pkg:cargo/ironrdp-tokio,
pkg:cargo/ironrdp-tls,
pkg:cargo/asn1-rs,
pkg:cargo/asn1-rs-derive,
pkg:cargo/asn1-rs-impl,
Expand Down
60 changes: 30 additions & 30 deletions Cargo.lock

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

26 changes: 13 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@ lto = "thin"
[workspace.dependencies]
# Note: To use a local IronRDP repository as a crate (for example, ironrdp-cliprdr), define the dependency as follows:
# ironrdp-cliprdr = { path = "/path/to/local/IronRDP/crates/ironrdp-cliprdr" }
ironrdp-cliprdr = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-connector = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-core = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-displaycontrol = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-dvc = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-graphics = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-pdu = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-rdpdr = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-rdpsnd = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-session = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-svc = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-tls = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73", features = [
ironrdp-cliprdr = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-connector = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-core = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-displaycontrol = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-dvc = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-graphics = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-pdu = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-rdpdr = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-rdpsnd = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-session = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-svc = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
ironrdp-tls = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2", features = [
"rustls",
] }
ironrdp-tokio = { git = "https://github.com/Devolutions/IronRDP", rev = "2f57fd2de320f58fe240d88a83519255ba94cb73" }
ironrdp-tokio = { git = "https://github.com/Devolutions/IronRDP", rev = "dd221bf22401c4635798ec012724cba7e6d503b2" }
8 changes: 8 additions & 0 deletions api/types/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,11 @@ type ListWindowsDesktopServicesRequest struct {
Labels map[string]string
SearchKeywords []string
}

// RDPLicenseKey is struct for retrieving licenses from backend cache, used only internally
type RDPLicenseKey struct {
Version uint32 // e.g. 0x000a0002
Issuer string // e.g. example.com
Company string // e.g. Example Corporation
ProductID string // e.g. A02
}
38 changes: 38 additions & 0 deletions lib/auth/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ package storage
import (
"context"
"encoding/json"
"strconv"
"strings"
"time"

"github.com/coreos/go-semver/semver"
"github.com/gravitational/trace"
Expand Down Expand Up @@ -233,6 +235,42 @@ func (p *ProcessStorage) WriteTeleportVersion(ctx context.Context, version *semv
return nil
}

func rdpLicenseKey(key *types.RDPLicenseKey) backend.Key {
return backend.NewKey("rdplicense", key.Issuer, strconv.Itoa(int(key.Version)), key.Company, key.ProductID)
}

type rdpLicense struct {
Data []byte `json:"data"`
}

// WriteRDPLicense writes an RDP license to local storage.
func (p *ProcessStorage) WriteRDPLicense(ctx context.Context, key *types.RDPLicenseKey, license []byte) error {
value, err := json.Marshal(rdpLicense{Data: license})
if err != nil {
return trace.Wrap(err)
}
item := backend.Item{
Key: rdpLicenseKey(key),
Value: value,
Expires: p.BackendStorage.Clock().Now().Add(28 * 24 * time.Hour),
}
_, err = p.stateStorage.Put(ctx, item)
return trace.Wrap(err)
}

// ReadRDPLicense reads a previously obtained license from storage.
func (p *ProcessStorage) ReadRDPLicense(ctx context.Context, key *types.RDPLicenseKey) ([]byte, error) {
item, err := p.stateStorage.Get(ctx, rdpLicenseKey(key))
if err != nil {
return nil, trace.Wrap(err)
}
license := rdpLicense{}
if err := json.Unmarshal(item.Value, &license); err != nil {
return nil, trace.Wrap(err)
}
return license.Data, nil
}

// ReadLocalIdentity reads, parses and returns the given pub/pri key + cert from the
// key storage (dataDir).
func ReadLocalIdentity(dataDir string, id state.IdentityID) (*state.Identity, error) {
Expand Down
72 changes: 72 additions & 0 deletions lib/auth/storage/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Teleport
// Copyright (C) 2025 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package storage

import (
"context"
"testing"

"github.com/gravitational/trace"
"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/backend/memory"
)

func TestRDPLicense(t *testing.T) {
ctx := context.Background()
mem, err := memory.New(memory.Config{})
require.NoError(t, err)
storage := ProcessStorage{
BackendStorage: mem,
stateStorage: mem,
}

_, err = storage.ReadRDPLicense(ctx, &types.RDPLicenseKey{
Version: 1,
Issuer: "issuer",
Company: "company",
ProductID: "productID",
})
require.True(t, trace.IsNotFound(err))

licenseData := []byte{1, 2, 3}
err = storage.WriteRDPLicense(ctx, &types.RDPLicenseKey{
Version: 1,
Issuer: "issuer",
Company: "company",
ProductID: "productID",
}, licenseData)
require.NoError(t, err)

_, err = storage.ReadRDPLicense(ctx, &types.RDPLicenseKey{
Version: 2,
Issuer: "issuer",
Company: "company",
ProductID: "productID",
})
require.True(t, trace.IsNotFound(err))

license, err := storage.ReadRDPLicense(ctx, &types.RDPLicenseKey{
Version: 1,
Issuer: "issuer",
Company: "company",
ProductID: "productID",
})
require.NoError(t, err)
require.Equal(t, licenseData, license)
}
1 change: 1 addition & 0 deletions lib/service/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(logger *slog

srv, err := desktop.NewWindowsService(desktop.WindowsServiceConfig{
DataDir: process.Config.DataDir,
LicenseStore: process.storage,
Logger: process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentWindowsDesktop, process.id)),
Clock: process.Clock,
Authorizer: authorizer,
Expand Down
Loading

0 comments on commit b57591d

Please sign in to comment.