microsoft
142 TopicsSingle-Sign-On mit Kerberos Constrained Delegation, Teil 2 - Debugging
Hallo liebe Leser, in meinem letzten Beitrag bin ich auf die Kerberos Constrained Delegation Konfiguration eingegangen und hatte darin bereits versprochen, in diesem Beitrag das Debugging als Thema zu wählen. Dadurch, dass wir Abhängigkeiten mit anderen Komponenten haben, wird das Debuggen sicher nicht einfacher. Grundsätzlich sollte im Fehlerfall aber überprüft werden, ob die BIG-IP denn die Active Directory (AD) Infrastruktur erfolgreich erreichen kann und ob die Zeit der BIG-IP auch gleich der des Key Distribution Center (KDC) ist. Hier hilft auf dem Command Line Interface (CLI) der Befehl „ntpq –pn“ um zu überprüfen, ob die Zeit richtig ist: Die Namensauflösung ist enorm wichtig bei Kerberos, da es davon abhängig ist. Ebenso auch die Reverse-DNS Auflösung, da dies auch verwendet wird um den SPN (Service Principal Name) für jeden Server nach der Loadbalancing Entscheidung zu bestimmen. Sollten die Namen nicht richtig im DNS eingetragen sein, so kann man diese auch auf der BIG-IP statisch hinterlegen. Das ist aber nur ein Workaround und sollte eigentlich vermieden werden. Hier ein Beispiel um einen Eintrag zu setzen: # tmsh sys global-settings remote-host add { server1 { addr 1.1.1.1 hostname sven } } Natürlich ist das auch über die GUI möglich. Hier nun ein paar Beispiele, wie man die Namensauflösung überprüfen kann. Der Befehl „nslookup“ hilft einem dabei: [root@bigip1-ve:Active] config # nslookup www.example.com Server: 10.0.0.25 Address: 10.0.0.25#53 Name: www.example.com Address: 10.0.0.250 [root@bigip1-ve:Active] config # nslookup 10.0.0.250 Server: 10.0.0.25 Address: 10.0.0.25#53 250.0.0.10.in-addr.arpa name = www.example.com. [root@bigip1-ve:Active] config # nslookup w2008r2.example.com Server: 10.0.0.25 Address: 10.0.0.25#53 ** server can't find w2008r2.example.com: NXDOMAIN [root@bigip1-ve:Active] config # nslookup 10.0.0.26 Server: 10.0.0.25 Address: 10.0.0.25#53 26.0.0.10.in-addr.arpa name = win2008r2.example.com. Da Kerberos SSO sich für die KDC-Ermittlung auf DNS verlässt (sofern hier keine Adresse in der SSO Konfiguration eingetragen wurde), sollte der DNS-Server auch SRV-Records haben, die auf den KDC-Server für den Realm der Domain zeigen. Wir erinnern uns hier, dass ich dies in der Konfiguration des letzten Blogs eingetragen habe. Die Begründung war, dass es dann schneller geht: Schließlich erspart man sich genau diese Auflöse-Prozedur an der Stelle. [root@bigip1-ve:Active] config # nslookup -type=srv _kerberos._tcp.example.com Server: 10.0.0.25 Address: 10.0.0.25#53 _kerberos._tcp.example.com service = 0 100 88 win2008r2.example.com. [root@bigip1-ve:Active] config # nslookup -type=srv _kerberos._udp.example.com Server: 10.0.0.25 Address: 10.0.0.25#53 _kerberos._udp.example.com service = 0 100 88 win2008r2.example.com. Sind diese grundlegenden Konfigurationen nun überprüft und korrekt, aber es funktioniert noch nicht, hilft ein Blick in das APM Logfile. Vorher sollte aber der Debug-Level höher eingestellt werden. Dies bitte nur während des debuggens machen und anschließend wieder zurück stellen. Im Menü kann man unter System/Logs/Configuration/Options den Level einstellen. „Informational“ ist hier ein guter Start. „Debug“ kann man sicher auch verwenden, aber der Level ist schon extrem gesprächig und in der Regel reicht „Informational“ vollkommen. Schauen wir uns doch mal einen Ausschnitt eines Anmeldeprozesses an. Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:862 Initialized UCC:user1@EXAMPLE.COM@EXAMPLE.COM, lifetime:36000 kcc:0x8ac52f0 Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:867 UCCmap.size = 1, UCClist.size = 1 Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:1058 S4U ======> - NO cached S4U2Proxy ticket for user: user1@EXAMPLE.COM server: HTTP/win 2008r2.example.com@EXAMPLE.COM - trying to fetch Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:1072 S4U ======> - NO cached S4U2Self ticket for user: user1@EXAMPLE.COM - trying to fetch Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:1082 S4U ======> - fetched S4U2Self ticket for user: user1@EXAMPLE.COM Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:1104 S4U ======> trying to fetch S4U2Proxy ticket for user: user1@EXAMPLE.COM server: HTTP/win2008r2.example.com@EXAMPLE.COM Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:1112 S4U ======> fetched S4U2Proxy ticket for user: user1@EXAMPLE.COM server: HTTP/win2008r2.example.com@EXAMPLE.COM Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:1215 S4U ======> OK! Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:236 GSSAPI: Server: HTTP/win2008r2.example.com@EXAMPLE.COM, User: user1@EXAMPLE.COM Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:278 GSSAPI Init_sec_context returned code 0 Oct 28 12:06:46 bigip1-ve debug /usr/bin/websso[400]: 01490000:7: <0xf1c75b90>:Modules/HttpHeaderBased/Kerberos.cpp:316 GSSAPI token of length 1451 bytes will be sent back Hilfreich ist es natürlich auch die Logfiles des IIS anzuschen. Hier kann man sich auch den Usernamen anzeigen lassen (C:\inetpub\logs\LogFiles\W3SVC1): #Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) sc-status sc-substatus sc-win32-status time-taken 2011-10-27 21:29:23 10.0.0.25 GET / - 80 EXAMPLE\user2 10.0.0.28 Mozilla/5.0+(compatible;+MSIE+9.0;+Windows+NT+6.1;+Trident/5.0) 200 0 0 15 Eine weitere Fehleranalysemöglichkeit ist natürlich sich anzuschauen, was denn für Daten über die Leitung gehen. Tcpdump und Wireshark sind hierbei unsere Freunde, # tcpdump –i internal –s0 –n–w /tmp/kerberos.pcap host 10.0.0.25 -i: das VLAN, die es zu belauschen gilt. Wählt man hier „0.0“ wird auf allen Schnittstellen gesniffert und man sieht die Pakete entsprechend oft. bzw. auch die Veränderungen, die ein Paket ggf. beim Durchlaufen der BIG-IP erfährt. -s0: bedeutet, dass das gesamte Paket aufgezeichnet werden soll -n: keine Namensauflösung -w /tmp/kerberos.pcap: Die Aufzeichnungen in die Datei /tmp/kerberos.cap speichern host 10.0.0.25: nur Pakete mit der Quell- oder Ziel-IP 10.0.0.25 aufzeichnen. Im Wireshark sieht eine Aufzeichnung dann etwa so aus: Wireshark versteht das Kerberos Protokoll und gibt somit gute Hinweise auf mögliche Probleme. Da APM die Kerberos Tickets cached, kann dies natürlich beim Testen zu unerwünschten Effekten führen. Das bedeutet es macht durchaus Sinn, während der Tests die Ticket Lifetime von standardmässig 600 Minuten runter zu setzen. Zudem kann man auch den Cache mit folgenden Befehl leeren: # bigstart restart websso Zum guten Schluß möchte ich dann noch ein paar Fehlermeldungen erläutern, die man im Log bei Problemen finden kann: Kerberos: can't get TGT for host/apm.realm.com@REALM.COM - Cannot find KDC for requested realm (-1765328230) – Hier sollte man überprüfen ob DNS funktioniert und der DNS Server alle SRV/A/PTR Records der involvierten Realms hat. In der Datei /etc/krb5.conf auch bitte überprüfen, ob dns_lookup_kdc auf True gesetzt ist. Den Eintrag findet man in dem Bereich [libdefaults]. Kerberos: can't get TGT for host/apm.realm.com@REALM.COM - Preauthentication failed (-1765328360) – Das Delegation Account Passwort ist falsch. Kerberos: can't get TGT for host/apm.realm.com@REALM.COM - Client not found in Kerberos database (-1765328378) – Der Delegation Account existiert nicht im AD. Kerberos: can't get TGT for host/apm.realm.com@REALM.COM - Clients credentials have been revoked (-1765328366) – Der Delegation Account ist im AD abgeschaltet. Kerberos: can't get TGT for host/apm.realm.com@realm.com - KDC reply did not match expectations (-1765328237) – Der Realm Name in der SSO Konfiguration muss groß geschrieben sein. Kerberos: can't get TGT for host/apm.realm.com@REALM.COM - A service is not available that is required to process the request (-1765328355) – Die Verbindung mit dem KDC ist nicht möglich. Gründe können z.B. sein: Firewall, TGS Service auf dem KDC läuft nicht oder es ist der falsche KDC angegeben… Kerberos: can't get S4U2Self ticket for user a@REALM.COM - Client not found in Kerberos database (-1765328378) – Es gibt den User 'a' nicht in der Domain REALM.COM. Kerberos: can't get S4U2Self ticket for user qq@REALM.COM - Ticket/authenticator don't match (-1765328348) – Der Delegation Account hat die Form UPN/SPN und entspricht nicht der verwendeten REALM Domain. Achtung, wenn man hier mit Cross-Realms arbeitet! Kerberos: can't get S4U2Self ticket for user aa@realm.com - Realm not local to KDC (-1765328316) – Das KDC Feld muss leer gelassen werden in der SSO Konfiguration, wenn man mit Cross-Realm SSO arbeitet. Also wenn ein User einen Server in einem anderen Realm erreichen muss. Kerberos: can't get S4U2Proxy ticket for server HTTP/webserver.realm.com@REALM.COM - Requesting ticket can't get forwardable tickets (-1765328163) – Der Delegation Account ist nicht für Constrained Delegation und/oder Protocol Transition konfiguriert. Kerberos: can't get S4U2Proxy ticket for server HTTP/webserver.realm.com@REALM.COM - KDC can't fulfill requested option (-1765328371) – Der Delegation Account in REALM.COM hat keinen Service für webserver.real.com in seiner Konfiguration. Damit Sie Kerberos auf der Windows Seite debuggen können, hilft vielleicht dieser Artikel: • http://www.microsoft.com/downloads/details.aspx?FamilyID=7DFEB015-6043-47DB-8238-DC7AF89C93F1&displaylang=en So, ich hoffe natürlich, dass Sie keine Probleme bei der Konfiguration des SSO mit Constrained Delegation haben, aber falls doch, hilft Ihnen hoffentlich dieser Blog-Beitrag, um dem Problem schneller auf die Spur zukommen. Ihr F5-Blogger, Sven Müller654Views1like1CommentAPM Cookbook: SAML IdP Chaining
As an APM subject mater expert at F5 I often find myself in situations where a customer or colleague needs an example of a particular configuration. While most of these requests are easily handled with a call or WebEx I'm a firm believer in sharing knowledge through documentation.. and I don't like getting calls at 3 AM. If you're like me you grew up with the O'Reilly Cookbook series which served as a great reference document for various development or server configuration tasks. My goal is to create a similar reference resource here on DevCentral for those one-off scenarios where a visual example may help your complete your task. For the first APM Cookbook series I'll discuss SAML IdP chaining. Overview Security Assertion Markup Language, more commonly known as SAML, is a popular federated authentication method that provides web based single sign-on. One of the key security advantages to SAML is the reduction in username/password combinations that a user has remember... or in my experience as a security engineer the number of passwords written on a post-it note stuck to their monitor. There are two major services in a SAML environment: IdP - Identity Provider SP - Service Provider The identity provider is the SAML service that authenticates the user and passes an assertion to then service providers proving the user's identity. F5's APM performs both IdP and SP services and allows customers to easily deploy federated authentication in their environment. In more complex scenarios you may run across a requirement where multiple SAML IdPs need to be chained together. This comes up from time to time when customers have contractors that utilize federated authentication for authorization to corporate resources. Example For our configuration we have the Globex Corporation that uses APM to authenticate uses to Office 365. Globex hire contractors from Acme Corp. who authenticate using the Acme Corp. ADFS environment. However, since Office 365 is configured to authenticate against the Globex APM we need to convert the Acme Corp. SAML assertion into a Globex SAML assertion, which is known as IdP chaining. The step ladder for this process is shown below: 1. User requests https://outlook.com/globex.com 2 - 3. Office 365 redirects user to idp.globex.com 3 - 4. idp.globex.com determines user is a contractor and redirect user to sts.acme.com 5 - 8. User authenticates using Acme credentials and is then redirect back to idp.globex.com 9. idp.globex.com consumes the Acme SAML assertion and creates a Globex SAML assertion 10. User is redirected back to Office 365 11 - 12. Office 365 consumes the Globex SAML assertion and displays the user's mail Configuration To configure your APM SAML IdP to accept incoming assertion from sts.acme.com we need to create an external SP connector. Under the Access Policy -> SAML -> BIG-IP as SP configuration section: 1. Create a new SAML SP Service 2. Export the SP metadata and configure sts.acme.com accordingly (follow your IdP vendor's documentation) 3. Click the External IdP Connectors menu at the top 4. Click the dropdown arrow on the create button and choose From Metadata (import the metadata from sts.acme.com) 5. Bind the Local SP service to the external IdP connector Now that idp.globex.com and sts.acme.com are configured to trust one another we need to configure the APM IdP to consume the sts.acme.com SAML assertion. The IdP's Visual Policy Editor should look similar to the image below: 1. The Decision Box asks the user what company they're with. This is a simple example but more elaborate home realm discovery techniques can be used. 2. The SAML Auth box is configured to consume the sts.acme.com assertion 3. Since we no longer have a login form on the IdP we need to set a few APM session variables: session.logon.last.username = Session Variable session.saml.last.identity session.logon.last.logonname = Session Variable session.saml.last.identity 4. Create an Advanced Resource Assign that matches your existing IdP Advance Resource Assign. Conclusion This particular post was a little longwinded due to the steps required but overall is a fairly simple configuration. So the next time someone asks if your F5 can do IdP chaining you can confidently reply "Yes and I know how to do that".4KViews1like6CommentsDeploying F5 BIG-IP in Microsoft Azure for Developers
F5’s BYOL (Bring Your Own License) model allows you to purchase a developer/lab license and install it in Microsoft Azure. This model provides a stable development instance for the following software components of BIG-IP: LTM, GTM, DNS, AFM, ASM, APM Lite (10 users), AAM, CGN, SSL Forward Proxy, Advanced Protocols, and Crypto Offload all at a 10Mbps rate limit. The Good/Better/Best options in Azure help guide you to suggested compute provisioning resources but since we’re deploying a Developer Lab license, we can chose any option and select a much lower cost virtual machine size. This allows MSDN Subscriptions with Azure credits to maintain a working F5 BIG-IP environment and stay within the monthly reoccurring billing cycles (assuming you power off (deallocate) your environment when not in use). Please refer to these resources to get started in Microsoft Azure How to get a F5 BIG-IP VE Developer Lab License F5 BIG-IP Virtual Edition Setup Guide for Microsoft Azure @ support.f5.com F5 BIG-IP ADC: Application and Security Services @ Microsoft Azure Marketplace Recommended Azure Computer Tiers for Developer & Lab Environments NOTE:DevCentral recommends 2 cores and 8GB to 14GB of RAM. The DevCental team uses D11 so we don’t have to reprovision as many modules for testing and lab work; we deallocate resources when we’re not using it to save cost. The BIG-IP Virtual Edition in Microsoft Azure at this time only supports 1 NIC so plan accordingly in regards to network security groups and network planning.4.4KViews1like2CommentsAPM Cookbook: Single Sign On (SSO) using Kerberos
To get the APM Cookbook series moving along, I’ve decided to help out by documenting the common APM solutions I help customers and partners with on a regular basis. Kerberos SSO is nothing new, but seems to stump people who have never used Kerberos before. Getting Kerberos SSO to work with APM is straight forward once you have the Active Directory components configured. Overview I have a pre-configured web service (IIS 7.5/Sharepoint 2010) that is configured for Windows Authentication, which will send a “Negotiate” in the header of the “401 Request for Authorization”. Make sure the web service is configured to send the correct header before starting the APM configuration by accessing the website directly and viewing the headers using browser tools. In my example, I used the Sharepoint 2010/2013 iApp to build the LTM configuration. I’m using a single pool member, sp1.f5.demo (10.10.30.2) listening on HTTP and the Virtual Server listening on HTTPS performing SSL offload. Step 1 - Create a delegation account on your domain 1.1 Open Active Directory Users and Computers administrative tool and create a new user account. User logon name: host/apm-kcd.f5.demo User logon name (pre-Windows 2000): apm-kcd Set the password and not expire 1.2 Alter the account and set the servicePrincipcalName. Run setspn from the command line: setspn –A host/apm-kcd.f5.demo apm-kcd A delegation tab will now be available for this user. Step 2 - Configure the SPN 2.1 Open Active Directory Users and Computers administrative tool and select the user account created in the previous step. Edit the Properties for this user Select the Delegation tab Select: Trust this user for delegation to specified services only Select: Use any authentication protocol Select Add, to add services. Select Users or Computers… Enter the host name, in my example I will be adding HTTP service for sp1.f5.demo (SP1). Select Check Names and OK Select the http Service Type and OK 2.2 Make sure there are no duplicate SPNs and run setspn –x from the command line. Step 3 - Check Forward and Reverse DNS DNS is critical and a missing PTR is common error I find when troubleshooting Kerberos SSO problems. From the BIG-IP command line test forward and reverse records exist for the web service using dig: # dig sp1.f5.demo ;; QUESTION SECTION: ;sp1.f5.demo. IN A ;; ANSWER SECTION: sp1.f5.demo. 1200 IN A 10.10.30.2 # dig -x 10.10.30.2 ;; QUESTION SECTION: ;2.30.10.10.in-addr.arpa. IN PTR ;; ANSWER SECTION: 2.30.10.10.in-addr.arpa. 1200 IN PTR sp1.f5.demo. Step 4 - Create the APM Configuration In this example I will use a Logon Page to capture the user credentials that will be authenticated against Active Directory and mapped to the SSO variables for the Kerberos SSO. 4.1 Configure AAA Server for Authentication Access Policy >> AAA Servers >> Active Directory >> “Create” Supply the following: Name: f5.demo_ad_aaa Domain Name: f5.demo Domain Controller: (Optional – BIG-IP will use DNS to discover if left blank) Admin Name and Password Select “Finished" to save. 4.2 Configure Kerberos SSO Access Policy >> SSO Configurations >> Kerberos >> “Create” Supply the following: Name: f5.demo_kerberos_sso Username Source: session.sso.token.last.username User Realm Source: session.ad.last.actualdomain Kerberos Realm: F5.DEMO Account Name: apm-kcd (from Step 1) Account Password & Confirm Account Password (from Step1) Select “Finished” to save. 4.3 Create an Access Profile and Policy We can now bring it all together using the Visual Policy Editor (VPE). Access Policy >> Access Profiles >> Access Profile List >> “Create” Supply the following: Name: intranet.f5.demo_sso_ap SSO Configuration: f5.demo_kerberos_sso Languages: English (en) Use the default settings for all other settings. Select “Finished” to save. 4.4 Edit the Access Policy in the VPE Access Policy >> Access Profiles >> Access Profile List >> “Edit” (intranet.f5.demo_sso_ap) On the fallback branch after the Start object, add a Logon Page object. Leave the defaults and “Save”. On the fallback branch after the Logon Page object, add an AD Auth object. Select the Server Select “Save” when your done. On the Successful branch after the AD Auth object, add a SSO Credential Mapping object. Leave the defaults and “Save”. On the fallback branch after the SSO Credential Mapping, change Deny ending to Allow. The finished policy should look similar to this: Don't forget to “Apply Access Policy”. Step 5 – Attach the APM Policy to the Virtual Server and Test 5.1 Edit the Virtual Server Local Traffic >> Virtual Servers >> Virtual Server List >> intranet.f5.demo_vs Scroll down to the Access Policy section and select the Access Profile. Select “Update” to save. 5.2 Test Open a browser, access the Virtual Server URL (https://intranet.f5.demo in my example), authenticate and verify the client is automatically logged on (SSO) to the web service. To verify Kerberos SSO has worked correctly, check /var/log/apm on APM by turning on debug. You should see log events similar to the ones below when the BIG-IP has fetched a Kerberos Ticket. info websso.1[9041]: 014d0011:6: 33186a8c: Websso Kerberos authentication for user 'test.user' using config '/Common/f5.demo_kerberos_sso' debug websso.1[9041]: 014d0018:7: sid:33186a8c ctx:0x917e4a0 server address = ::ffff:10.10.30.2 debug websso.1[9041]: 014d0021:7: sid:33186a8c ctx:0x917e4a0 SPN = HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0023:7: S4U ======> ctx: 33186a8c, sid: 0x917e4a0, user: test.user@F5.DEMO, SPN: HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0001:7: Getting UCC:test.user@F5.DEMO@F5.DEMO, lifetime:36000 debug websso.1[9041]: 014d0001:7: fetched new TGT, total active TGTs:1 debug websso.1[9041]: 014d0001:7: TGT: client=apm-kcd@F5.DEMO server=krbtgt/F5.DEMO@F5.DEMO expiration=Tue Apr 29 08:33:42 2014 flags=40600000 debug websso.1[9041]: 014d0001:7: TGT expires:1398724422 CC count:0 debug websso.1[9041]: 014d0001:7: Initialized UCC:test.user@F5.DEMO@F5.DEMO, lifetime:36000 kcc:0x92601e8 debug websso.1[9041]: 014d0001:7: UCCmap.size = 1, UCClist.size = 1 debug websso.1[9041]: 014d0001:7: S4U ======> - NO cached S4U2Proxy ticket for user: test.user@F5.DEMO server: HTTP/sp1.f5.demo@F5.DEMO - trying to fetch debug websso.1[9041]: 014d0001:7: S4U ======> - NO cached S4U2Self ticket for user: test.user@F5.DEMO - trying to fetch debug websso.1[9041]: 014d0001:7: S4U ======> - fetched S4U2Self ticket for user: test.user@F5.DEMO debug websso.1[9041]: 014d0001:7: S4U ======> trying to fetch S4U2Proxy ticket for user: test.user@F5.DEMO server: HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0001:7: S4U ======> fetched S4U2Proxy ticket for user: test.user@F5.DEMO server: HTTP/sp1.f5.demo@F5.DEMO debug websso.1[9041]: 014d0001:7: S4U ======> OK! Conclusion Like I said in the beginning, once you know how Kerberos SSO works with APM, it’s a piece of cake!8.2KViews1like28Comments