blob: 7a0f29cdbe107d1af4f7f5a2b4cf03a0bc1fda60 [file] [log] [blame]
// Copyright 2024 The Update Framework Authors
//
// 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
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"fmt"
"io"
stdlog "log"
"net/http"
"net/url"
"os"
"path/filepath"
"github.com/go-logr/stdr"
"github.com/theupdateframework/go-tuf/v2/metadata"
"github.com/theupdateframework/go-tuf/v2/metadata/config"
"github.com/theupdateframework/go-tuf/v2/metadata/updater"
)
// The following config is used to fetch a target from Jussi's GitHub repository example
const (
metadataURL = "https://jku.github.io/tuf-demo/metadata"
targetsURL = "https://jku.github.io/tuf-demo/targets"
targetName = "rdimitrov/artifact-example.md"
verbosity = 4
generateRandomFolder = false
)
func main() {
// set logger to stdout with info level
metadata.SetLogger(stdr.New(stdlog.New(os.Stdout, "client_example", stdlog.LstdFlags)))
stdr.SetVerbosity(verbosity)
log := metadata.GetLogger()
// initialize environment - temporary folders, etc.
metadataDir, err := InitEnvironment()
if err != nil {
log.Error(err, "Failed to initialize environment")
}
// initialize client with Trust-On-First-Use
err = InitTrustOnFirstUse(metadataDir)
if err != nil {
log.Error(err, "Trust-On-First-Use failed")
}
// download the desired target
err = DownloadTarget(metadataDir, targetName)
if err != nil {
log.Error(err, "Download failed")
}
}
// InitEnvironment prepares the local environment - temporary folders, etc.
func InitEnvironment() (string, error) {
var tmpDir string
// get working directory
cwd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get current working directory: %w", err)
}
if !generateRandomFolder {
tmpDir = filepath.Join(cwd, "tmp")
// create a temporary folder for storing the demo artifacts
os.Mkdir(tmpDir, 0750)
} else {
// create a temporary folder for storing the demo artifacts
tmpDir, err = os.MkdirTemp(cwd, "tmp")
if err != nil {
return "", fmt.Errorf("failed to create a temporary folder: %w", err)
}
}
// create a destination folder for storing the downloaded target
os.Mkdir(filepath.Join(tmpDir, "download"), 0750)
return tmpDir, nil
}
// InitTrustOnFirstUse initialize local trusted metadata (Trust-On-First-Use)
func InitTrustOnFirstUse(metadataDir string) error {
// check if there's already a local root.json available for bootstrapping trust
_, err := os.Stat(filepath.Join(metadataDir, "root.json"))
if err == nil {
return nil
}
// download the initial root metadata so we can bootstrap Trust-On-First-Use
rootURL, err := url.JoinPath(metadataURL, "1.root.json")
if err != nil {
return fmt.Errorf("failed to create URL path for 1.root.json: %w", err)
}
req, err := http.NewRequest("GET", rootURL, nil)
if err != nil {
return fmt.Errorf("failed to create http request: %w", err)
}
client := http.DefaultClient
res, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to executed the http request: %w", err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
return fmt.Errorf("failed to read the http request body: %w", err)
}
// write the downloaded root metadata to file
err = os.WriteFile(filepath.Join(metadataDir, "root.json"), data, 0644)
if err != nil {
return fmt.Errorf("failed to write root.json metadata: %w", err)
}
return nil
}
// DownloadTarget downloads the target file using Updater. The Updater refreshes the top-level metadata,
// get the target information, verifies if the target is already cached, and in case it
// is not cached, downloads the target file.
func DownloadTarget(localMetadataDir, target string) error {
log := metadata.GetLogger()
rootBytes, err := os.ReadFile(filepath.Join(localMetadataDir, "root.json"))
if err != nil {
return err
}
// create updater configuration
cfg, err := config.New(metadataURL, rootBytes) // default config
if err != nil {
return err
}
cfg.LocalMetadataDir = localMetadataDir
cfg.LocalTargetsDir = filepath.Join(localMetadataDir, "download")
cfg.RemoteTargetsURL = targetsURL
cfg.PrefixTargetsWithHash = true
// create a new Updater instance
up, err := updater.New(cfg)
if err != nil {
return fmt.Errorf("failed to create Updater instance: %w", err)
}
// try to build the top-level metadata
err = up.Refresh()
if err != nil {
return fmt.Errorf("failed to refresh trusted metadata: %w", err)
}
// search if the desired target is available
targetInfo, err := up.GetTargetInfo(target)
if err != nil {
return fmt.Errorf("target %s not found: %w", target, err)
}
// target is available, so let's see if the target is already present locally
path, _, err := up.FindCachedTarget(targetInfo, "")
if err != nil {
return fmt.Errorf("failed while finding a cached target: %w", err)
}
if path != "" {
log.Info("Target is already present", "target", target, "path", path)
}
// target is not present locally, so let's try to download it
path, _, err = up.DownloadTarget(targetInfo, "", "")
if err != nil {
return fmt.Errorf("failed to download target file %s - %w", target, err)
}
log.Info("Successfully downloaded target", "target", target, "path", path)
return nil
}