-
Notifications
You must be signed in to change notification settings - Fork 55
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
chore: use config block list instead of allow list #247
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright 2024 Jigsaw Operations LLC | ||
// | ||
// 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 | ||
// | ||
// https://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. | ||
|
||
//go:build psiphon | ||
|
||
package psiphon | ||
|
||
// Fake variable used in the package to require the build tag, without preventing the rest of the code | ||
// from being processed by the Go documentation service. | ||
var mustSetPsiphonBuildTag = struct{}{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,26 +12,20 @@ | |
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//go:build psiphon | ||
|
||
package psiphon | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net" | ||
"runtime" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/Jigsaw-Code/outline-sdk/transport" | ||
"github.com/Psiphon-Labs/psiphon-tunnel-core/ClientLibrary/clientlib" | ||
psi "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" | ||
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters" | ||
) | ||
|
||
// The single [Dialer] we can have. | ||
|
@@ -45,130 +39,16 @@ var ( | |
errTunnelTimeout = errors.New("tunnel establishment timed out") | ||
) | ||
|
||
// ClientInfo specifies information about the client app that should be communicated | ||
// to Psiphon for authentication and metrics. | ||
type ClientInfo struct { | ||
// PropagationChannelId is a string identifier which indicates how the | ||
// Psiphon client was distributed. This parameter is required. This value | ||
// is supplied by and depends on the Psiphon Network. | ||
PropagationChannelId string | ||
|
||
// SponsorId is a string identifier which indicates who is sponsoring this | ||
// Psiphon client. This parameter is required. This value is supplied | ||
// by and depends on the Psiphon Network. | ||
SponsorId string | ||
|
||
// ClientVersion is the client version number that the client reports to | ||
// the server. The version number refers to the host client application, | ||
// not the core tunnel library. | ||
ClientVersion string | ||
|
||
// ClientPlatform is the client platform ("Windows", "Android", etc.) that | ||
// the client reports to the server. | ||
ClientPlatform string | ||
} | ||
|
||
// ServerListConfig specifies how Psiphon obtains server lists. | ||
type ServerListConfig struct { | ||
// ObfuscatedServerListRootURLs is a list of URLs which specify root | ||
// locations from which to fetch obfuscated server list files. This value | ||
// is supplied by and depends on the Psiphon Network, and is typically | ||
// embedded in the client binary. All URLs must point to the same entity | ||
// with the same ETag. At least one DownloadURL must have | ||
// OnlyAfterAttempts = 0. | ||
ObfuscatedServerListRootURLs parameters.TransferURLs | ||
|
||
// RemoteServerListURLs is list of URLs which specify locations to fetch | ||
// out-of-band server entries. This facility is used when a tunnel cannot | ||
// be established to known servers. This value is supplied by and depends | ||
// on the Psiphon Network, and is typically embedded in the client binary. | ||
// All URLs must point to the same entity with the same ETag. At least one | ||
// TransferURL must have OnlyAfterAttempts = 0. | ||
RemoteServerListURLs parameters.TransferURLs | ||
|
||
// RemoteServerListSignaturePublicKey specifies a public key that's used | ||
// to authenticate the remote server list payload. This value is supplied | ||
// by and depends on the Psiphon Network, and is typically embedded in the | ||
// client binary. | ||
RemoteServerListSignaturePublicKey string | ||
|
||
// ServerEntrySignaturePublicKey is a base64-encoded, ed25519 public | ||
// key value used to verify individual server entry signatures. This value | ||
// is supplied by and depends on the Psiphon Network, and is typically | ||
// embedded in the client binary. | ||
ServerEntrySignaturePublicKey string | ||
|
||
// TargetServerEntry is an encoded server entry. When specified, this | ||
// server entry is used exclusively and all other known servers are | ||
// ignored; also, when set, ConnectionWorkerPoolSize is ignored and | ||
// the pool size is 1. | ||
TargetServerEntry string | ||
} | ||
|
||
// StorageConfig specifies where Psiphon should store its data. | ||
type StorageConfig struct { | ||
// DataRootDirectory is the directory in which to store persistent files, | ||
// which contain information such as server entries. By default, current | ||
// working directory. | ||
// | ||
// Psiphon will assume full control of files under this directory. They may | ||
// be deleted, moved or overwritten. | ||
// DialerConfig specifies the parameters for [Dialer]. | ||
type DialerConfig struct { | ||
// Used as the directory for the datastore, remote server list, and obfuscasted | ||
// server list. | ||
// Empty string means the default will be used (current working directory). | ||
// Strongly recommended. | ||
DataRootDirectory string | ||
} | ||
|
||
// Config specifies how the Psiphon dialer should behave. | ||
type Config struct { | ||
ClientInfo | ||
ServerListConfig | ||
StorageConfig | ||
} | ||
|
||
// extendedConfig allows us to evaluate some settings that are present in the Psiphon config, | ||
// but the user shouldn't be specifying. | ||
type extendedConfig struct { | ||
Config | ||
|
||
// DisableLocalHTTPProxy disables running the local HTTP proxy. | ||
DisableLocalHTTPProxy *bool | ||
// DisableLocalSocksProxy disables running the local SOCKS proxy. | ||
DisableLocalSocksProxy *bool | ||
// TargetApiProtocol specifies whether to force use of "ssh" or "web" API | ||
// protocol. When blank, the default, the optimal API protocol is used. | ||
// Note that this capability check is not applied before the | ||
// "CandidateServers" count is emitted. | ||
// | ||
// This parameter is intended for testing and debugging only. Not all | ||
// parameters are supported in the legacy "web" API protocol, including | ||
// speed test samples. | ||
TargetApiProtocol *string | ||
} | ||
|
||
// ParseConfig parses the config JSON into a structure that can be further edited. | ||
func ParseConfig(configJSON []byte) (*Config, error) { | ||
var extCfg extendedConfig | ||
|
||
// Set default values | ||
extCfg.ClientPlatform = strings.ReplaceAll(fmt.Sprintf("OutlineSDK_%s_%s", runtime.GOOS, runtime.GOARCH), " ", "_") | ||
|
||
decoder := json.NewDecoder(bytes.NewReader(configJSON)) | ||
decoder.DisallowUnknownFields() | ||
err := decoder.Decode(&extCfg) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse config: %w", err) | ||
} | ||
|
||
// We ignore these fields in the config, but only if they are set to the proper values. | ||
if extCfg.DisableLocalHTTPProxy != nil && !*extCfg.DisableLocalHTTPProxy { | ||
return nil, fmt.Errorf("DisableLocalHTTPProxy must be true if set") | ||
} | ||
if extCfg.DisableLocalSocksProxy != nil && !*extCfg.DisableLocalSocksProxy { | ||
return nil, fmt.Errorf("DisableLocalSocksProxy must be true if set") | ||
} | ||
if extCfg.TargetApiProtocol != nil && *extCfg.TargetApiProtocol != "ssh" { | ||
return nil, fmt.Errorf(`TargetApiProtocol must be "ssh" if set`) | ||
} | ||
|
||
return &extCfg.Config, nil | ||
// Raw JSON config provided by Psiphon. | ||
ProviderConfig json.RawMessage | ||
} | ||
|
||
// Dialer is a [transport.StreamDialer] that uses Psiphon to connect to a destination. | ||
|
@@ -208,21 +88,51 @@ func (d *Dialer) DialStream(unusedContext context.Context, addr string) (transpo | |
return streamConn{netConn}, nil | ||
} | ||
|
||
// Start configures and runs the Dialer. It must be called before you can use the Dialer. It returns when the tunnel is ready. | ||
func (d *Dialer) Start(ctx context.Context, config *Config) error { | ||
configJSON, err := json.Marshal(config) | ||
if err != nil { | ||
return fmt.Errorf("failed to convert config to JSON") | ||
func newPsiphonConfig(config *DialerConfig) (*psi.Config, error) { | ||
if config == nil { | ||
return nil, errors.New("config must not be nil") | ||
} | ||
pConfig, err := psi.LoadConfig(configJSON) | ||
// Validate keys. We parse as a map first because we need to check for the existence | ||
// of certain keys. | ||
var configMap map[string]interface{} | ||
if err := json.Unmarshal(config.ProviderConfig, &configMap); err != nil { | ||
return nil, fmt.Errorf("failed to parse config: %w", err) | ||
} | ||
for key, value := range configMap { | ||
switch key { | ||
case "DisableLocalHTTPProxy", "DisableLocalSocksProxy": | ||
b, ok := value.(bool) | ||
if !ok { | ||
return nil, fmt.Errorf("field %v must be a boolean", key) | ||
} | ||
if b != true { | ||
return nil, fmt.Errorf("field %v must be true if set", key) | ||
} | ||
case "DataRootDirectory": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we allow customers to specify their own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because that must be specified via the DialerConfig.DataRootDirectory. This makes it clear what they should be setting vs what is provided by Psiphon. |
||
return nil, errors.New("field DataRootDirectory must not be set in the provider config. Specify it in the DialerConfig instead.") | ||
} | ||
} | ||
|
||
// Parse provider config. | ||
pConfig, err := psi.LoadConfig(config.ProviderConfig) | ||
if err != nil { | ||
return fmt.Errorf("config load failed: %w", err) | ||
return nil, fmt.Errorf("config load failed: %w", err) | ||
} | ||
|
||
// Override some Psiphon defaults. | ||
// Force some Psiphon config defaults for the Outline SDK case. | ||
pConfig.DisableLocalHTTPProxy = true | ||
pConfig.DisableLocalSocksProxy = true | ||
pConfig.TargetApiProtocol = "ssh" | ||
pConfig.DataRootDirectory = config.DataRootDirectory | ||
|
||
return pConfig, nil | ||
} | ||
|
||
// Start configures and runs the Dialer. It must be called before you can use the Dialer. It returns when the tunnel is ready. | ||
func (d *Dialer) Start(ctx context.Context, config *DialerConfig) error { | ||
pConfig, err := newPsiphonConfig(config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Will receive a value if an error occurs during the connection sequence. | ||
// It will be closed on succesful connection. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are still digging into a specific field in the config. Not sure whether this is breaking the "black-box" or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because those fields are incompatible with the Outline SDK. The key change in this PR is from specifying what fields to allow to specifying what fields to now allow. So Psiphon config - disallowed fields is a black box now.