jthomas opened a new issue #12: Swift SDK does not support self-signed server certificates. URL: https://github.com/apache/incubator-openwhisk-runtime-swift/issues/12 Local instances of the platform use a self-signed SSL certificate. The Swift SDK (`_Whisk.swift`) fails when invoked against platform endpoints without a valid SSL certificate. The JavaScript SDK supports a constructor argument to turn off certificat checking to resolve this issue. **Looking into implementing this behaviour for the Swift SDK, I have discovered a blocking issue due to the lack of support in the open-source Swift Foundation libraries.** In Swift, certificate checking can be turned off by creating a new `URLSessionDelegate` with always trusts the server. ```swift class SessionDelegate:NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential) } } ``` This can be used when creating the `URLSession` to use based upon a method parameter. ```swift let session = ignoreCerts ? URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) : URLSession(configuration: URLSessionConfiguration.default) ``` This works on OS X but compiling the code on Linux, I ran into the following issue. ``` _Whisky.swift:25:39: error: incorrect argument label in call (have 'trust:', expected 'coder:') let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) ^~~~~~ coder root@3a4fde570648:/source# swift -v Swift version 4.0 (swift-4.0-RELEASE) Target: x86_64-unknown-linux-gnu ``` Looking into the source code for the Swift foundation core libraries, I discovered this method has not been implemented. https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/URLCredential.swift#L155 ``` // TODO: We have no implementation for Security.framework primitive types SecIdentity and SecTrust yet ``` Talking to the IBM@Swift team, support for this feature is being worked on but won't be available until Swift 5 at the earliest. Until then, we will have to document the behaviour and wait for the foundation libraries to catch up. I've attached the completed `_Whisk.swift` demonstrating the bug. ```swift /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ import Foundation import Dispatch class SessionDelegate:NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential) } } class Whisk { static var baseUrl = ProcessInfo.processInfo.environment["__OW_API_HOST"] static var apiKey = ProcessInfo.processInfo.environment["__OW_API_KEY"] class func invoke(actionNamed action : String, withParameters params : [String:Any], ignoreCerts: Bool = false, blocking: Bool = true) -> [String:Any] { let parsedAction = parseQualifiedName(name: action) let strBlocking = blocking ? "true" : "false" let path = "/api/v1/namespaces/\(parsedAction.namespace)/actions/\(parsedAction.name)?blocking=\(strBlocking)" return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "POST", ignoreCerts: ignoreCerts) } class func trigger(eventNamed event : String, ignoreCerts: Bool = false, withParameters params : [String:Any]) -> [String:Any] { let parsedEvent = parseQualifiedName(name: event) let path = "/api/v1/namespaces/\(parsedEvent.namespace)/triggers/\(parsedEvent.name)?blocking=true" return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "POST", ignoreCerts: ignoreCerts) } class func createTrigger(triggerNamed trigger: String, ignoreCerts: Bool = false, withParameters params : [String:Any]) -> [String:Any] { let parsedTrigger = parseQualifiedName(name: trigger) let path = "/api/v1/namespaces/\(parsedTrigger.namespace)/triggers/\(parsedTrigger.name)" return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "PUT", ignoreCerts: ignoreCerts) } class func createRule(ruleNamed ruleName: String, withTrigger triggerName: String, andAction actionName: String, ignoreCerts: Bool = false) -> [String:Any] { let parsedRule = parseQualifiedName(name: ruleName) let path = "/api/v1/namespaces/\(parsedRule.namespace)/rules/\(parsedRule.name)" let params = ["trigger":triggerName, "action":actionName] return sendWhiskRequestSyncronish(uriPath: path, params: params, method: "PUT", ignoreCerts: ignoreCerts) } // handle the GCD dance to make the post async, but then obtain/return // the result from this function sync private class func sendWhiskRequestSyncronish(uriPath path: String, params : [String:Any], method: String, ignoreCerts: Bool) -> [String:Any] { var response : [String:Any]! let queue = DispatchQueue.global() let invokeGroup = DispatchGroup() invokeGroup.enter() queue.async { postUrlSession(uriPath: path, ignoreCerts: ignoreCerts, params: params, method: method, group: invokeGroup) { result in response = result } } // On one hand, FOREVER seems like an awfully long time... // But on the other hand, I think we can rely on the system to kill this // if it exceeds a reasonable execution time. switch invokeGroup.wait(timeout: DispatchTime.distantFuture) { case DispatchTimeoutResult.success: break case DispatchTimeoutResult.timedOut: break } return response } /** * Using new UrlSession */ private class func postUrlSession(uriPath: String, ignoreCerts: Bool, params : [String:Any], method: String,group: DispatchGroup, callback : @escaping([String:Any]) -> Void) { guard let encodedPath = uriPath.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else { callback(["error": "Error encoding uri path to make openwhisk REST call."]) return } let urlStr = "\(baseUrl!)\(encodedPath)" if let url = URL(string: urlStr) { var request = URLRequest(url: url) request.httpMethod = method do { request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONSerialization.data(withJSONObject: params) let loginData: Data = apiKey!.data(using: String.Encoding.utf8, allowLossyConversion: false)! let base64EncodedAuthKey = loginData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) request.addValue("Basic \(base64EncodedAuthKey)", forHTTPHeaderField: "Authorization") let session = ignoreCerts ? URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) : URLSession(configuration: URLSessionConfiguration.default) let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in // exit group after we are done defer { group.leave() } if let error = error { callback(["error":error.localizedDescription]) } else { if let data = data { do { //let outputStr = String(data: data, encoding: String.Encoding.utf8) as String! //print(outputStr) let respJson = try JSONSerialization.jsonObject(with: data) if respJson is [String:Any] { callback(respJson as! [String:Any]) } else { callback(["error":" response from server is not a dictionary"]) } } catch { callback(["error":"Error creating json from response: \(error)"]) } } } }) task.resume() } catch { callback(["error":"Got error creating params body: \(error)"]) } } } // separate an OpenWhisk qualified name (e.g. "/whisk.system/samples/date") // into namespace and name components private class func parseQualifiedName(name qualifiedName : String) -> (namespace : String, name : String) { let defaultNamespace = "_" let delimiter = "/" let segments :[String] = qualifiedName.components(separatedBy: delimiter) if segments.count > 2 { return (segments[1], Array(segments[2..<segments.count]).joined(separator: delimiter)) } else if segments.count == 2 { // case "/action" or "package/action" let name = qualifiedName.hasPrefix(delimiter) ? segments[1] : segments.joined(separator: delimiter) return (defaultNamespace, name) } else { return (defaultNamespace, segments[0]) } } } ```
---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services