|
|
@@ -39,8 +39,9 @@ import (
|
|
|
)
|
|
|
|
|
|
const (
|
|
|
- SERVER_CONFIG_FILENAME = "psiphon-server.config"
|
|
|
- SERVER_ENTRY_FILENAME = "serverEntry.dat"
|
|
|
+ SERVER_CONFIG_FILENAME = "psiphond.config"
|
|
|
+ SERVER_TRAFFIC_RULES_FILENAME = "psiphond-traffic-rules.config"
|
|
|
+ SERVER_ENTRY_FILENAME = "server-entry.dat"
|
|
|
DEFAULT_SERVER_IP_ADDRESS = "127.0.0.1"
|
|
|
WEB_SERVER_SECRET_BYTE_LENGTH = 32
|
|
|
DISCOVERY_VALUE_KEY_BYTE_LENGTH = 32
|
|
|
@@ -57,8 +58,6 @@ const (
|
|
|
GEOIP_SESSION_CACHE_TTL = 60 * time.Minute
|
|
|
)
|
|
|
|
|
|
-// TODO: break config into sections (sub-structs)
|
|
|
-
|
|
|
// Config specifies the configuration and behavior of a Psiphon
|
|
|
// server.
|
|
|
type Config struct {
|
|
|
@@ -81,9 +80,8 @@ type Config struct {
|
|
|
// Fail2BanFormat is a string format specifier for the
|
|
|
// log message format to use for fail2ban integration for
|
|
|
// blocking abusive clients by source IP address.
|
|
|
- // When set, logs with this format are made to the AUTH
|
|
|
- // facility with INFO severity in the local syslog server
|
|
|
- // if clients fail to authenticate.
|
|
|
+ // When set, logs with this format are made if clients fail
|
|
|
+ // to authenticate.
|
|
|
// The client's IP address is included with the log message.
|
|
|
// An example format specifier, which should be compatible
|
|
|
// with default SSH fail2ban configuration, is
|
|
|
@@ -203,100 +201,16 @@ type Config struct {
|
|
|
// tunneled DNS UDP packets will be re-routed to this destination.
|
|
|
UDPForwardDNSServerAddress string
|
|
|
|
|
|
- // DefaultTrafficRules specifies the traffic rules to be used when
|
|
|
- // no regional-specific rules are set.
|
|
|
- DefaultTrafficRules TrafficRules
|
|
|
-
|
|
|
- // RegionalTrafficRules specifies the traffic rules for particular
|
|
|
- // client regions (countries) as determined by GeoIP lookup of the
|
|
|
- // client IP address. The key for each regional traffic rule entry
|
|
|
- // is one or more space delimited ISO 3166-1 alpha-2 country codes.
|
|
|
- RegionalTrafficRules map[string]TrafficRules
|
|
|
-
|
|
|
// LoadMonitorPeriodSeconds indicates how frequently to log server
|
|
|
// load information (number of connected clients per tunnel protocol,
|
|
|
// number of running goroutines, amount of memory allocated, etc.)
|
|
|
// The default, 0, disables load logging.
|
|
|
LoadMonitorPeriodSeconds int
|
|
|
-}
|
|
|
-
|
|
|
-// RateLimits specify the rate limits for tunneled data transfer
|
|
|
-// between an individual client and the server.
|
|
|
-type RateLimits struct {
|
|
|
|
|
|
- // DownstreamUnlimitedBytes specifies the number of downstream
|
|
|
- // bytes to transfer, approximately, before starting rate
|
|
|
- // limiting.
|
|
|
- DownstreamUnlimitedBytes int64
|
|
|
-
|
|
|
- // DownstreamBytesPerSecond specifies a rate limit for downstream
|
|
|
- // data transfer. The default, 0, is no limit.
|
|
|
- DownstreamBytesPerSecond int
|
|
|
-
|
|
|
- // UpstreamUnlimitedBytes specifies the number of upstream
|
|
|
- // bytes to transfer, approximately, before starting rate
|
|
|
- // limiting.
|
|
|
- UpstreamUnlimitedBytes int64
|
|
|
-
|
|
|
- // UpstreamBytesPerSecond specifies a rate limit for upstream
|
|
|
- // data transfer. The default, 0, is no limit.
|
|
|
- UpstreamBytesPerSecond int
|
|
|
-}
|
|
|
-
|
|
|
-// TrafficRules specify the limits placed on client traffic.
|
|
|
-type TrafficRules struct {
|
|
|
- // DefaultRateLimitsare the rate limits to be applied when
|
|
|
- // no protocol-specific rates are set.
|
|
|
- DefaultRateLimits RateLimits
|
|
|
-
|
|
|
- // ProtocolRateLimits specifies the rate limits for particular
|
|
|
- // tunnel protocols. The key for each rate limit entry is one
|
|
|
- // or more space delimited Psiphon tunnel protocol names. Valid
|
|
|
- // tunnel protocols includes the same list as for
|
|
|
- // TunnelProtocolPorts.
|
|
|
- ProtocolRateLimits map[string]RateLimits
|
|
|
-
|
|
|
- // IdleTCPPortForwardTimeoutMilliseconds is the timeout period
|
|
|
- // after which idle (no bytes flowing in either direction)
|
|
|
- // client TCP port forwards are preemptively closed.
|
|
|
- // The default, 0, is no idle timeout.
|
|
|
- IdleTCPPortForwardTimeoutMilliseconds int
|
|
|
-
|
|
|
- // IdleUDPPortForwardTimeoutMilliseconds is the timeout period
|
|
|
- // after which idle (no bytes flowing in either direction)
|
|
|
- // client UDP port forwards are preemptively closed.
|
|
|
- // The default, 0, is no idle timeout.
|
|
|
- IdleUDPPortForwardTimeoutMilliseconds int
|
|
|
-
|
|
|
- // MaxTCPPortForwardCount is the maximum number of TCP port
|
|
|
- // forwards each client may have open concurrently.
|
|
|
- // The default, 0, is no maximum.
|
|
|
- MaxTCPPortForwardCount int
|
|
|
-
|
|
|
- // MaxUDPPortForwardCount is the maximum number of UDP port
|
|
|
- // forwards each client may have open concurrently.
|
|
|
- // The default, 0, is no maximum.
|
|
|
- MaxUDPPortForwardCount int
|
|
|
-
|
|
|
- // AllowTCPPorts specifies a whitelist of TCP ports that
|
|
|
- // are permitted for port forwarding. When set, only ports
|
|
|
- // in the list are accessible to clients.
|
|
|
- AllowTCPPorts []int
|
|
|
-
|
|
|
- // AllowUDPPorts specifies a whitelist of UDP ports that
|
|
|
- // are permitted for port forwarding. When set, only ports
|
|
|
- // in the list are accessible to clients.
|
|
|
- AllowUDPPorts []int
|
|
|
-
|
|
|
- // DenyTCPPorts specifies a blacklist of TCP ports that
|
|
|
- // are not permitted for port forwarding. When set, the
|
|
|
- // ports in the list are inaccessible to clients.
|
|
|
- DenyTCPPorts []int
|
|
|
-
|
|
|
- // DenyUDPPorts specifies a blacklist of UDP ports that
|
|
|
- // are not permitted for port forwarding. When set, the
|
|
|
- // ports in the list are inaccessible to clients.
|
|
|
- DenyUDPPorts []int
|
|
|
+ // TrafficRulesFilename is the path of a file containing a
|
|
|
+ // JSON-encoded TrafficRulesSet, the traffic rules to apply to
|
|
|
+ // Psiphon client tunnels.
|
|
|
+ TrafficRulesFilename string
|
|
|
}
|
|
|
|
|
|
// RunWebServer indicates whether to run a web server component.
|
|
|
@@ -315,49 +229,13 @@ func (config *Config) UseFail2Ban() bool {
|
|
|
return config.Fail2BanFormat != ""
|
|
|
}
|
|
|
|
|
|
-// GetTrafficRules looks up the traffic rules for the specified country. If there
|
|
|
-// are no RegionalTrafficRules for the country, DefaultTrafficRules are used.
|
|
|
-func (config *Config) GetTrafficRules(clientCountryCode string) TrafficRules {
|
|
|
- // TODO: faster lookup?
|
|
|
- for countryCodes, trafficRules := range config.RegionalTrafficRules {
|
|
|
- for _, countryCode := range strings.Split(countryCodes, " ") {
|
|
|
- if countryCode == clientCountryCode {
|
|
|
- return trafficRules
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return config.DefaultTrafficRules
|
|
|
-}
|
|
|
+// LoadConfig loads and validates a JSON encoded server config.
|
|
|
+func LoadConfig(configJSON []byte) (*Config, error) {
|
|
|
|
|
|
-// GetRateLimits looks up the rate limits for the specified tunnel protocol.
|
|
|
-// If there are no ProtocolRateLimits for the protocol, DefaultRateLimits are used.
|
|
|
-func (rules *TrafficRules) GetRateLimits(clientTunnelProtocol string) RateLimits {
|
|
|
- // TODO: faster lookup?
|
|
|
- for tunnelProtocols, rateLimits := range rules.ProtocolRateLimits {
|
|
|
- for _, tunnelProtocol := range strings.Split(tunnelProtocols, " ") {
|
|
|
- if tunnelProtocol == clientTunnelProtocol {
|
|
|
- return rateLimits
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return rules.DefaultRateLimits
|
|
|
-}
|
|
|
-
|
|
|
-// LoadConfig loads and validates a JSON encoded server config. If more than one
|
|
|
-// JSON config is specified, then all are loaded and values are merged together,
|
|
|
-// in order. Multiple configs allows for use cases like storing static, server-specific
|
|
|
-// values in a base config while also deploying network-wide throttling settings
|
|
|
-// in a secondary file that can be paved over on all server hosts.
|
|
|
-func LoadConfig(configJSONs [][]byte) (*Config, error) {
|
|
|
-
|
|
|
- // Note: default values are set in GenerateConfig
|
|
|
var config Config
|
|
|
-
|
|
|
- for _, configJSON := range configJSONs {
|
|
|
- err := json.Unmarshal(configJSON, &config)
|
|
|
- if err != nil {
|
|
|
- return nil, psiphon.ContextError(err)
|
|
|
- }
|
|
|
+ err := json.Unmarshal(configJSON, &config)
|
|
|
+ if err != nil {
|
|
|
+ return nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
if config.Fail2BanFormat != "" && strings.Count(config.Fail2BanFormat, "%s") != 1 {
|
|
|
@@ -442,25 +320,26 @@ type GenerateConfigParams struct {
|
|
|
WebServerPort int
|
|
|
EnableSSHAPIRequests bool
|
|
|
TunnelProtocolPorts map[string]int
|
|
|
+ TrafficRulesFilename string
|
|
|
}
|
|
|
|
|
|
-// GenerateConfig creates a new Psiphon server config. It returns a JSON
|
|
|
-// encoded config and a client-compatible "server entry" for the server. It
|
|
|
+// GenerateConfig creates a new Psiphon server config. It returns JSON
|
|
|
+// encoded configs and a client-compatible "server entry" for the server. It
|
|
|
// generates all necessary secrets and key material, which are emitted in
|
|
|
// the config file and server entry as necessary.
|
|
|
// GenerateConfig uses sample values for many fields. The intention is for
|
|
|
-// a generated config to be used for testing or as a template for production
|
|
|
+// generated configs to be used for testing or as a template for production
|
|
|
// setup, not to generate production-ready configurations.
|
|
|
-func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
+func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, error) {
|
|
|
|
|
|
// Input validation
|
|
|
|
|
|
if net.ParseIP(params.ServerIPAddress) == nil {
|
|
|
- return nil, nil, psiphon.ContextError(errors.New("invalid IP address"))
|
|
|
+ return nil, nil, nil, psiphon.ContextError(errors.New("invalid IP address"))
|
|
|
}
|
|
|
|
|
|
if len(params.TunnelProtocolPorts) == 0 {
|
|
|
- return nil, nil, psiphon.ContextError(errors.New("no tunnel protocols"))
|
|
|
+ return nil, nil, nil, psiphon.ContextError(errors.New("no tunnel protocols"))
|
|
|
}
|
|
|
|
|
|
usedPort := make(map[int]bool)
|
|
|
@@ -473,11 +352,11 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
for protocol, port := range params.TunnelProtocolPorts {
|
|
|
|
|
|
if !psiphon.Contains(psiphon.SupportedTunnelProtocols, protocol) {
|
|
|
- return nil, nil, psiphon.ContextError(errors.New("invalid tunnel protocol"))
|
|
|
+ return nil, nil, nil, psiphon.ContextError(errors.New("invalid tunnel protocol"))
|
|
|
}
|
|
|
|
|
|
if usedPort[port] {
|
|
|
- return nil, nil, psiphon.ContextError(errors.New("duplicate listening port"))
|
|
|
+ return nil, nil, nil, psiphon.ContextError(errors.New("duplicate listening port"))
|
|
|
}
|
|
|
usedPort[port] = true
|
|
|
|
|
|
@@ -495,12 +374,12 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
var err error
|
|
|
webServerSecret, err = psiphon.MakeRandomStringHex(WEB_SERVER_SECRET_BYTE_LENGTH)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
webServerCertificate, webServerPrivateKey, err = GenerateWebServerCertificate("")
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -509,7 +388,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
// TODO: use other key types: anti-fingerprint by varying params
|
|
|
rsaKey, err := rsa.GenerateKey(rand.Reader, SSH_RSA_HOST_KEY_BITS)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
sshPrivateKey := pem.EncodeToMemory(
|
|
|
@@ -521,21 +400,21 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
|
|
|
signer, err := ssh.NewSignerFromKey(rsaKey)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
sshPublicKey := signer.PublicKey()
|
|
|
|
|
|
sshUserNameSuffix, err := psiphon.MakeRandomStringHex(SSH_USERNAME_SUFFIX_BYTE_LENGTH)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
sshUserName := "psiphon_" + sshUserNameSuffix
|
|
|
|
|
|
sshPassword, err := psiphon.MakeRandomStringHex(SSH_PASSWORD_BYTE_LENGTH)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
// TODO: vary version string for anti-fingerprint
|
|
|
@@ -545,7 +424,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
|
|
|
obfuscatedSSHKey, err := psiphon.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
// Meek config
|
|
|
@@ -556,7 +435,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
rawMeekCookieEncryptionPublicKey, rawMeekCookieEncryptionPrivateKey, err :=
|
|
|
box.GenerateKey(rand.Reader)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
meekCookieEncryptionPublicKey = base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPublicKey[:])
|
|
|
@@ -564,7 +443,7 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
|
|
|
meekObfuscatedKey, err = psiphon.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -572,10 +451,10 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
|
|
|
discoveryValueHMACKey, err := psiphon.MakeRandomStringBase64(DISCOVERY_VALUE_KEY_BYTE_LENGTH)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
- // Assemble config and server entry
|
|
|
+ // Assemble configs and server entry
|
|
|
|
|
|
// Note: this config is intended for either testing or as an illustrative
|
|
|
// example or template and is not intended for production deployment.
|
|
|
@@ -606,8 +485,18 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
MeekCertificateCommonName: "www.example.org",
|
|
|
MeekProhibitedHeaders: nil,
|
|
|
MeekProxyForwardedForHeaders: []string{"X-Forwarded-For"},
|
|
|
- DefaultTrafficRules: TrafficRules{
|
|
|
- DefaultRateLimits: RateLimits{
|
|
|
+ LoadMonitorPeriodSeconds: 300,
|
|
|
+ TrafficRulesFilename: params.TrafficRulesFilename,
|
|
|
+ }
|
|
|
+
|
|
|
+ encodedConfig, err := json.MarshalIndent(config, "\n", " ")
|
|
|
+ if err != nil {
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ trafficRulesSet := &TrafficRulesSet{
|
|
|
+ DefaultRules: TrafficRules{
|
|
|
+ DefaultLimits: RateLimits{
|
|
|
DownstreamUnlimitedBytes: 0,
|
|
|
DownstreamBytesPerSecond: 0,
|
|
|
UpstreamUnlimitedBytes: 0,
|
|
|
@@ -622,18 +511,13 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
DenyTCPPorts: nil,
|
|
|
DenyUDPPorts: nil,
|
|
|
},
|
|
|
- LoadMonitorPeriodSeconds: 300,
|
|
|
}
|
|
|
|
|
|
- encodedConfig, err := json.MarshalIndent(config, "\n", " ")
|
|
|
+ encodedTrafficRulesSet, err := json.MarshalIndent(trafficRulesSet, "\n", " ")
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
- // Server entry format omits the BEGIN/END lines and newlines
|
|
|
- lines := strings.Split(webServerCertificate, "\n")
|
|
|
- strippedWebServerCertificate := strings.Join(lines[1:len(lines)-2], "")
|
|
|
-
|
|
|
capabilities := []string{}
|
|
|
|
|
|
if params.EnableSSHAPIRequests {
|
|
|
@@ -664,9 +548,14 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
// a fronting hop.
|
|
|
|
|
|
serverEntryWebServerPort := ""
|
|
|
+ strippedWebServerCertificate := ""
|
|
|
|
|
|
if params.WebServerPort != 0 {
|
|
|
serverEntryWebServerPort = fmt.Sprintf("%d", params.WebServerPort)
|
|
|
+
|
|
|
+ // Server entry format omits the BEGIN/END lines and newlines
|
|
|
+ lines := strings.Split(webServerCertificate, "\n")
|
|
|
+ strippedWebServerCertificate = strings.Join(lines[1:len(lines)-2], "")
|
|
|
}
|
|
|
|
|
|
serverEntry := &psiphon.ServerEntry{
|
|
|
@@ -692,8 +581,8 @@ func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {
|
|
|
|
|
|
encodedServerEntry, err := psiphon.EncodeServerEntry(serverEntry)
|
|
|
if err != nil {
|
|
|
- return nil, nil, psiphon.ContextError(err)
|
|
|
+ return nil, nil, nil, psiphon.ContextError(err)
|
|
|
}
|
|
|
|
|
|
- return encodedConfig, []byte(encodedServerEntry), nil
|
|
|
+ return encodedConfig, encodedTrafficRulesSet, []byte(encodedServerEntry), nil
|
|
|
}
|